diff --git a/.hgignore b/.hgignore
new file mode 100644
index 00000000000..4c1284b47dd
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,23 @@
+syntax: glob
+
+# Downloaded and locally built eggs
+eggs
+scripts/scramble/build
+scripts/scramble/lib
+scripts/scramble/archives
+
+# Database stuff
+database/beaker_sessions
+database/compiled_templates
+database/files
+database/*.sqlite
+
+# Python bytecode
+*.pyc
+
+# Config files
+universe_wsgi.ini
+reports_wsgi.ini
+datatypes_conf.xml
+tool_conf.xml
+
diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py
index 267b63f1f1e..fc325b33601 100644
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -39,6 +39,7 @@ class Configuration( object ):
self.tool_config = resolve_path( kwargs.get( 'tool_config_file', 'tool_conf.xml' ), self.root )
self.tool_secret = kwargs.get( "tool_secret", "" )
self.id_secret = kwargs.get( "id_secret", "USING THE DEFAULT IS NOT SECURE!" )
+ self.set_metadata_externally = string_as_bool( kwargs.get( "set_metadata_externally", "False" ) )
self.use_remote_user = string_as_bool( kwargs.get( "use_remote_user", "False" ) )
self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None )
self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) )
@@ -112,7 +113,17 @@ class Configuration( object ):
for path in self.tool_config, self.datatypes_config:
if not os.path.isfile(path):
raise ConfigurationError("File not found: %s" % path )
-
+
+ def is_admin_user( self,user ):
+ """
+ Determine if the provided user is listed in `admin_users`.
+
+ NOTE: This is temporary, admin users will likely be specified in the
+ database in the future.
+ """
+ admin_users = self.get( "admin_users", "" ).split( "," )
+ return ( user is not None and user.email in admin_users )
+
def get_database_engine_options( kwargs ):
"""
Allow options for the SQLAlchemy database engine to be passed by using
diff --git a/lib/galaxy/datatypes/interval.py b/lib/galaxy/datatypes/interval.py
index 5e4fdcaa4cc..f0c4236e2e8 100644
--- a/lib/galaxy/datatypes/interval.py
+++ b/lib/galaxy/datatypes/interval.py
@@ -731,12 +731,16 @@ class Wiggle( Tabular ):
if line and not line.startswith( '#' ):
elems = line.split( '\t' )
try:
- int( elems[0] )
+ float( elems[0] ) #"Wiggle track data values can be integer or real, positive or negative values"
break
except:
+ do_break = False
for str in data.col1_startswith:
if elems[0].lower().startswith(str):
+ do_break = True
break
+ if do_break:
+ break
Tabular.set_meta( self, dataset, overwrite = overwrite, skip = i )
def sniff( self, filename ):
diff --git a/lib/galaxy/datatypes/metadata.py b/lib/galaxy/datatypes/metadata.py
index f0e6e9d6958..8865cbfcda7 100644
--- a/lib/galaxy/datatypes/metadata.py
+++ b/lib/galaxy/datatypes/metadata.py
@@ -1,6 +1,6 @@
-import sys, logging, copy, shutil, weakref
+import sys, logging, copy, shutil, weakref, cPickle, tempfile, os
-from galaxy.util import string_as_bool
+from galaxy.util import string_as_bool, relpath
from galaxy.util.odict import odict
from galaxy.web import form_builder
import galaxy.model
@@ -9,6 +9,8 @@ log = logging.getLogger( __name__ )
STATEMENTS = "__galaxy_statements__" #this is the name of the property in a Datatype class where new metadata spec element Statements are stored
+DATABASE_CONNECTION_AVAILABLE = True #When False, certain metadata parameter types (see FileParameter) will behave differently
+
class Statement( object ):
"""
This class inserts its target into a list in the surrounding
@@ -28,7 +30,7 @@ class Statement( object ):
statement.target( element, *args, **kwargs ) #statement.target is MetadataElementSpec, element is a Datatype class
-class MetadataCollection:
+class MetadataCollection( object ):
"""
MetadataCollection is not a collection at all, but rather a proxy
to the real metadata which is stored as a Dictionary. This class
@@ -90,6 +92,26 @@ class MetadataCollection:
if key in self.spec:
rval[key] = self.spec[key].param.make_copy( value, target_context=self, source_context=to_copy )
return rval
+ def from_pickled_dict( self, filename ):
+ dataset = self.parent
+ log.debug( 'loading metadata from file for: %s %s' % ( dataset.__class__.__name__, dataset.id ) )
+ pickled_dict = cPickle.load( open( filename ) )
+ for name, spec in self.spec.items():
+ if name in pickled_dict:
+ dataset._metadata[ name ] = spec.param.from_pickle_value( pickled_dict[ name ], dataset )
+ elif name in dataset._metadata:
+ #if the metadata value is not found in our externally set metadata but it has a value in the 'old'
+ #metadata associated with our dataset, we'll delete it from our dataset's metadata dict
+ del dataset._metadata[ name ]
+ def to_pickled_dict( self, filename ):
+ meta_dict = {}
+ dataset_meta_dict = self.parent._metadata
+ for name, spec in self.spec.items():
+ if name in dataset_meta_dict:
+ meta_dict[ name ] = spec.param.to_pickle_value( dataset_meta_dict[ name ] )
+ cPickle.dump( meta_dict, open( filename, 'wb+' ) )
+ def __getstate__( self ):
+ return None #cannot pickle a weakref item (self._parent), when data._metadata_collection is None, it will be recreated on demand
class MetadataSpecCollection( odict ):
"""
@@ -168,6 +190,17 @@ class MetadataParameter( object ):
"""
return value
+ def from_pickle_value( self, value, parent ):
+ """
+ Turns a value read from a pickled dict into its value to be pushed directly into the metadata dict.
+ """
+ return value
+ def to_pickle_value( self, value ):
+ """
+ Turns a value read from a metadata into its value to be pushed directly into the pickled dict.
+ """
+ return value
+
class MetadataElementSpec( object ):
"""
Defines a metadata element and adds it to the metadata_spec (which
@@ -326,14 +359,19 @@ class FileParameter( MetadataParameter ):
return "
No display available for Metadata Files
"
def wrap( self, value ):
- if isinstance( value, galaxy.model.MetadataFile ):
+ if isinstance( value, galaxy.model.MetadataFile ) or isinstance( value, MetadataTempFile ):
return value
- try:
- return galaxy.model.MetadataFile.get( value )
- except:
- #value was not a valid id
- return None
-
+ if DATABASE_CONNECTION_AVAILABLE:
+ try:
+ return galaxy.model.MetadataFile.get( value )
+ except:
+ #value was not a valid id
+ return None
+ elif value is not None:
+ mf = galaxy.model.MetadataFile()
+ mf.id = value #we assume this is a valid id, since we cannot check it
+ return mf
+ return None
def make_copy( self, value, target_context = None, source_context = None ):
value = self.wrap( value )
if value:
@@ -342,9 +380,146 @@ class FileParameter( MetadataParameter ):
shutil.copy( value.file_name, new_value.file_name )
return self.unwrap( new_value )
return None
-
+
@classmethod
def marshal( cls, value ):
if isinstance( value, galaxy.model.MetadataFile ):
value = value.id
return value
+
+ def from_pickle_value( self, value, parent ):
+ """
+ Turns a value read from a pickled dict into its value to be pushed directly into the metadata dict.
+ """
+ if isinstance( value, MetadataTempFile ):
+ mf = self.new_file( dataset = parent, **value.kwd )
+ shutil.move( value.file_name, mf.file_name )
+ value = mf.id
+ return value
+ def to_pickle_value( self, value ):
+ """
+ Turns a value read from a metadata into its value to be pushed directly into the pickled dict.
+ """
+ if isinstance( value, galaxy.model.MetadataFile ):
+ value = value.id
+ return value
+
+ def new_file( self, dataset = None, **kwd ):
+ if DATABASE_CONNECTION_AVAILABLE:
+ mf = galaxy.model.MetadataFile( name = self.spec.name, dataset = dataset, **kwd )
+ mf.flush() #flush to assign id
+ return mf
+ else:
+ #we need to make a tmp file that is accessable to the head node,
+ #we will be copying its contents into the MetadataFile objects filename after restoring from pickle
+ #we do not include 'dataset' in the kwds passed, as from_pickle_value() will handle this for us
+ return MetadataTempFile( **kwd )
+
+#This class is used when a database file connection is not available
+class MetadataTempFile( object ):
+ tmp_dir = 'database/tmp' #this should be overwritten as necessary in calling scripts
+ def __init__( self, **kwd ):
+ self.kwd = kwd
+ self._filename = None
+ @property
+ def file_name( self ):
+ if self._filename is None:
+ #we need to create a tmp file, accessable across all nodes/heads, save the name, and return it
+ self._filename = relpath( tempfile.NamedTemporaryFile( dir = self.tmp_dir, prefix = "metadata_temp_file_" ).name )
+ open( self._filename, 'wb+' ) #create an empty file, so it can't be reused using tempfile
+ return self._filename
+ @classmethod
+ def cleanup_from_pickled_dict_filename( cls, filename ):
+ try:
+ for key, value in cPickle.load( open( filename ) ).items():
+ if isinstance( value, cls ) and os.path.exists( value.file_name ):
+ log.debug( 'Cleaning up abandoned MetadataTempFile file: %s' % value.file_name )
+ os.unlink( value.file_name )
+ except Exception, e:
+ log.debug( 'Failed to cleanup MetadataTempFile temp files from %s: %s' % ( filename, e ) )
+
+#Class with methods allowing set_meta() to be called externally to the Galaxy head
+class JobExternalOutputMetadataWrapper( object ):
+ #this class allows access to external metadata filenames for all outputs associated with a job
+ def __init__( self, job ):
+ self.job_id = job.id
+ def get_output_filenames_by_dataset( self, dataset ):
+ if isinstance( dataset, galaxy.model.HistoryDatasetAssociation ):
+ return galaxy.model.JobExternalOutputMetadata.filter_by( job_id = self.job_id, history_dataset_association_id = dataset.id ).first() #there should only be one or None
+ elif isinstance( dataset, galaxy.model.LibraryDatasetDatasetAssociation ):
+ return galaxy.model.JobExternalOutputMetadata.filter_by( job_id = self.job_id, library_dataset_dataset_association_id = dataset.id ).first() #there should only be one or None
+ return None
+ def get_dataset_metadata_key( self, dataset ):
+ return "%s_%d" % ( dataset.__class__.__name__, dataset.id ) #set meta can be called on library items and history items, need to make different keys for them, since ids can overlap
+ def setup_external_metadata( self, datasets, exec_dir = None, tmp_dir = None, dataset_files_path = None, kwds = {} ):
+ #fill in metadata_files_dict and return the command with args required to set metadata
+ def __metadata_files_list_to_cmd_line( metadata_files ):
+ return "%s,%s,%s,%s" % ( metadata_files.filename_in, metadata_files.filename_kwds, metadata_files.filename_out, metadata_files.filename_results_code )
+ if not isinstance( datasets, list ):
+ datasets = [ datasets ]
+ if exec_dir is None:
+ exec_dir = os.path.abspath( os.getcwd() )
+ if tmp_dir is None:
+ tmp_dir = MetadataTempFile.tmp_dir
+ if dataset_files_path is None:
+ dataset_files_path = galaxy.model.Dataset.file_path
+ metadata_files_list = []
+ for dataset in datasets:
+ key = self.get_dataset_metadata_key( dataset )
+ #future note:
+ #wonkiness in job execution causes build command line to be called more than once
+ #when setting metadata externally, via 'auto-detect' button in edit attributes, etc.,
+ #we don't want to overwrite (losing the ability to cleanup) our existing dataset keys and files,
+ #so we will only populate the dictionary once
+ metadata_files = self.get_output_filenames_by_dataset( dataset )
+ if not metadata_files:
+ metadata_files = galaxy.model.JobExternalOutputMetadata( dataset = dataset)
+ metadata_files.job_id = self.job_id
+ #we are using tempfile to create unique filenames, tempfile always returns an absolute path
+ #we will use pathnames relative to the galaxy root, to accommodate instances where the galaxy root
+ #is located differently, i.e. on a cluster node with a different filesystem structure
+
+ #file to store existing dataset
+ metadata_files.filename_in = relpath( tempfile.NamedTemporaryFile( dir = tmp_dir, prefix = "metadata_in_%s_" % key ).name )
+ cPickle.dump( dataset, open( metadata_files.filename_in, 'wb+' ) )
+ #file to store metadata results of set_meta()
+ metadata_files.filename_out = relpath( tempfile.NamedTemporaryFile( dir = tmp_dir, prefix = "metadata_out_%s_" % key ).name )
+ open( metadata_files.filename_out, 'wb+' ) # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible)
+ #file to store a 'return code' indicating the results of the set_meta() call
+ #results code is like (True/False - if setting metadata was successful/failed , exception or string of reason of success/failure )
+ metadata_files.filename_results_code = relpath( tempfile.NamedTemporaryFile( dir = tmp_dir, prefix = "metadata_out_%s_" % key ).name )
+ cPickle.dump( ( False, 'External set_meta() not called' ), open( metadata_files.filename_results_code, 'wb+' ) ) # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible)
+ #file to store kwds passed to set_meta()
+ metadata_files.filename_kwds = relpath( tempfile.NamedTemporaryFile( dir = tmp_dir, prefix = "metadata_kwds_%s_" % key ).name )
+ cPickle.dump( kwds, open( metadata_files.filename_kwds, 'wb+' ) )
+ metadata_files.flush()
+ metadata_files_list.append( metadata_files )
+ #return command required to build
+ return "%s %s %s %s" % ( os.path.join( exec_dir, 'set_metadata.sh' ), dataset_files_path, tmp_dir, " ".join( map( __metadata_files_list_to_cmd_line, metadata_files_list ) ) )
+
+ def external_metadata_set_successfully( self, dataset ):
+ metadata_files = self.get_output_filenames_by_dataset( dataset )
+ if not metadata_files:
+ return False # this file doesn't exist
+ rval, rstring = cPickle.load( open( metadata_files.filename_results_code ) )
+ if not rval:
+ log.debug( 'setting metadata externally failed for %s %s: %s' % ( dataset.__class__.__name__, dataset.id, rstring ) )
+ return rval
+
+ def cleanup_external_metadata( self ):
+ log.debug( 'Cleaning up external metadata files' )
+ for metadata_files in galaxy.model.Job.get( self.job_id ).external_output_metadata:
+ #we need to confirm that any MetadataTempFile files were removed, if not we need to remove them
+ #can occur if the job was stopped before completion, but a MetadataTempFile is used in the set_meta
+ MetadataTempFile.cleanup_from_pickled_dict_filename( metadata_files.filename_out )
+ dataset_key = self.get_dataset_metadata_key( metadata_files.dataset )
+ for key, fname in [ ( 'filename_in', metadata_files.filename_in ), ( 'filename_out', metadata_files.filename_out ), ( 'filename_results_code', metadata_files.filename_results_code ), ( 'filename_kwds', metadata_files.filename_kwds ) ]:
+ try:
+ os.remove( fname )
+ except Exception, e:
+ log.debug( 'Failed to cleanup external metadata file (%s) for %s: %s' % ( key, dataset_key, e ) )
+ def set_job_runner_external_pid( self, pid ):
+ for metadata_files in galaxy.model.Job.get( self.job_id ).external_output_metadata:
+ metadata_files.job_runner_external_pid = pid
+ metadata_files.flush()
+
diff --git a/lib/galaxy/datatypes/sequence.py b/lib/galaxy/datatypes/sequence.py
index 10d54cffa03..d8d9f076af9 100644
--- a/lib/galaxy/datatypes/sequence.py
+++ b/lib/galaxy/datatypes/sequence.py
@@ -254,8 +254,7 @@ class Maf( Alignment ):
tmp_file.write( "%s\t%s\n" % ( spec, "\t".join( chroms ) ) )
if not chrom_file:
- chrom_file = galaxy.model.MetadataFile( dataset = dataset, name = "species_chromosomes" )
- chrom_file.flush()
+ chrom_file = dataset.metadata.spec['species_chromosomes'].param.new_file( dataset = dataset )
tmp_file.seek( 0 )
open( chrom_file.file_name, 'wb' ).write( tmp_file.read() )
dataset.metadata.species_chromosomes = chrom_file
@@ -263,8 +262,7 @@ class Maf( Alignment ):
index_file = dataset.metadata.maf_index
if not index_file:
- index_file = galaxy.model.MetadataFile( dataset = dataset, name="maf_index" )
- index_file.flush()
+ index_file = dataset.metadata.spec['maf_index'].param.new_file( dataset = dataset )
indexes.write( open( index_file.file_name, 'w' ) )
dataset.metadata.maf_index = index_file
diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py
index 792da30a250..91962e3c9ff 100644
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -4,6 +4,7 @@ from galaxy import util, model
from galaxy.model import mapping
from galaxy.datatypes.tabular import *
from galaxy.datatypes.interval import *
+from galaxy.datatypes import metadata
import pkg_resources
pkg_resources.require( "PasteDeploy" )
@@ -306,6 +307,7 @@ class JobWrapper( object ):
self.working_directory = \
os.path.join( self.app.config.job_working_directory, str( self.job_id ) )
self.output_paths = None
+ self.external_output_metadata = metadata.JobExternalOutputMetadataWrapper( job ) #wrapper holding the info required to restore and clean up from pickled files used for setting metadata externally
def get_param_dict( self ):
"""
@@ -462,6 +464,7 @@ class JobWrapper( object ):
self.fail( "Job %s's output dataset(s) could not be read" % job.id )
return
for dataset_assoc in job.output_datasets:
+ #should this also be checking library associations? - can a library item be added from a history before the job has ended? - lets not allow this to occur
for dataset in dataset_assoc.dataset.dataset.history_associations: #need to update all associated output hdas, i.e. history was shared with job running
dataset.blurb = 'done'
dataset.peek = 'no peek'
@@ -470,13 +473,25 @@ class JobWrapper( object ):
if stderr:
dataset.blurb = "error"
elif dataset.has_data():
- # Only set metadata values if they are missing...
- dataset.set_meta( overwrite = False )
+ #if a dataset was copied, it won't appear in our dictionary:
+ #either use the metadata from originating output dataset, or call set_meta on the copies
+ #it would be quicker to just copy the metadata from the originating output dataset,
+ #but somewhat trickier (need to recurse up the copied_from tree), for now we'll call set_meta()
+ if not self.external_output_metadata.external_metadata_set_successfully( dataset ):
+ # Only set metadata values if they are missing...
+ dataset.set_meta( overwrite = False )
+ else:
+ #load metadata from file
+ #we need to no longer allow metadata to be edited while the job is still running,
+ #since if it is edited, the metadata changed on the running output will no longer match
+ #the metadata that was stored to disk for use via the external process,
+ #and the changes made by the user will be lost, without warning or notice
+ dataset.metadata.from_pickled_dict( self.external_output_metadata.get_output_filenames_by_dataset( dataset ).filename_out )
dataset.set_peek()
else:
dataset.blurb = "empty"
dataset.flush()
- if stderr:
+ if stderr:
dataset_assoc.dataset.dataset.state = model.Dataset.states.ERROR
else:
dataset_assoc.dataset.dataset.state = model.Dataset.states.OK
@@ -517,10 +532,12 @@ class JobWrapper( object ):
def cleanup( self ):
# remove temporary files
try:
- for fname in self.extra_filenames:
+ for fname in self.extra_filenames:
os.remove( fname )
if self.working_directory is not None:
shutil.rmtree( self.working_directory )
+ if self.app.config.set_metadata_externally:
+ self.external_output_metadata.cleanup_external_metadata()
except:
log.exception( "Unable to cleanup job %d" % self.job_id )
@@ -573,7 +590,15 @@ class JobWrapper( object ):
for outfile in [ str( o ) for o in output_paths ]:
sizes.append( ( outfile, os.stat( outfile ).st_size ) )
return sizes
-
+ def setup_external_metadata( self, exec_dir = None, tmp_dir = None, dataset_files_path = None, **kwds ):
+ if tmp_dir is None:
+ #this dir should should relative to the exec_dir
+ tmp_dir = self.app.config.new_file_path
+ if dataset_files_path is None:
+ dataset_files_path = self.app.model.Dataset.file_path
+ job = model.Job.get( self.job_id )
+ return self.external_output_metadata.setup_external_metadata( [ output_dataset_assoc.dataset for output_dataset_assoc in job.output_datasets ], exec_dir = exec_dir, tmp_dir = tmp_dir, dataset_files_path = dataset_files_path, **kwds )
+
class DefaultJobDispatcher( object ):
def __init__( self, app ):
self.app = app
diff --git a/lib/galaxy/jobs/runners/local.py b/lib/galaxy/jobs/runners/local.py
index a2d675a70b6..7d07a04a0da 100644
--- a/lib/galaxy/jobs/runners/local.py
+++ b/lib/galaxy/jobs/runners/local.py
@@ -99,6 +99,21 @@ class LocalJobRunner( object ):
job_wrapper.fail( "failure running job", exception=True )
log.exception("failure running job %d" % job_wrapper.job_id)
return
+
+ #run the metadata setting script here
+ #this is terminatable when output dataset/job is deleted
+ #so that long running set_meta()s can be cancelled without having to reboot the server
+ if job_wrapper.get_state() not in [ model.Job.states.ERROR, model.Job.states.DELETED ] and self.app.config.set_metadata_externally:
+ external_metadata_script = job_wrapper.setup_external_metadata( kwds = { 'overwrite' : False } ) #we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior
+ log.debug( 'executing external set_meta script for job %d: %s' % ( job_wrapper.job_id, external_metadata_script ) )
+ external_metadata_proc = subprocess.Popen( args = external_metadata_script,
+ shell = True,
+ env = env,
+ preexec_fn = os.setpgrp )
+ job_wrapper.external_output_metadata.set_job_runner_external_pid( external_metadata_proc.pid )
+ external_metadata_proc.wait()
+ log.debug( 'execution of external set_meta finished for job %d' % job_wrapper.job_id )
+
# Finish the job
try:
job_wrapper.finish( stdout, stderr )
@@ -131,12 +146,17 @@ class LocalJobRunner( object ):
return False
def stop_job( self, job ):
- if job.job_runner_external_id is None:
+ #if our local job has JobExternalOutputMetadata associated, then our primary job has to have already finished
+ if job.external_output_metadata:
+ pid = job.external_output_metadata[0].job_runner_external_pid #every JobExternalOutputMetadata has a pid set, we just need to take from one of them
+ else:
+ pid = job.job_runner_external_id
+ if pid in [ None, '' ]:
log.warning( "stop_job(): %s: no PID in database for job, unable to stop" % job.id )
return
- pid = int( job.job_runner_external_id )
+ pid = int( pid )
if not self.check_pid( pid ):
- log.warning( "stop_job(): %s: PID %d was already dead or can't be signaled" %job.id )
+ log.warning( "stop_job(): %s: PID %d was already dead or can't be signaled" % ( job.id, pid ) )
return
for sig in [ 15, 9 ]:
try:
diff --git a/lib/galaxy/jobs/runners/pbs.py b/lib/galaxy/jobs/runners/pbs.py
index 91c0044effc..257ca66b4de 100644
--- a/lib/galaxy/jobs/runners/pbs.py
+++ b/lib/galaxy/jobs/runners/pbs.py
@@ -27,6 +27,7 @@ if [ "$GALAXY_LIB" != "None" ]; then
fi
cd %s
%s
+%s
"""
pbs_symlink_template = """#!/bin/sh
@@ -208,7 +209,11 @@ class PBSJobRunner( object ):
if self.app.config.pbs_stage_path != '':
script = pbs_symlink_template % (job_wrapper.galaxy_lib_dir, " ".join(job_wrapper.get_input_fnames() + job_wrapper.get_output_fnames()), self.app.config.pbs_stage_path, exec_dir, command_line)
else:
- script = pbs_template % (job_wrapper.galaxy_lib_dir, exec_dir, command_line)
+ if self.app.config.set_metadata_externally:
+ external_metadata_script = job_wrapper.setup_external_metadata( exec_dir = exec_dir, tmp_dir = self.app.config.new_file_path, dataset_files_path = self.app.model.Dataset.file_path, kwds = { 'overwrite' : False } ) #we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior
+ else:
+ external_metadata_script = ""
+ script = pbs_template % ( job_wrapper.galaxy_lib_dir, exec_dir, command_line, external_metadata_script )
job_file = "%s/%s.sh" % (self.app.config.cluster_files_directory, job_wrapper.job_id)
fh = file(job_file, "w")
fh.write(script)
diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py
index 598e443b8fa..70a28e80766 100644
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -141,6 +141,21 @@ class JobToOutputDatasetAssociation( object ):
self.name = name
self.dataset = dataset
+class JobExternalOutputMetadata( object ):
+ def __init__( self, job = None, dataset = None ):
+ self.job = job
+ if isinstance( dataset, galaxy.model.HistoryDatasetAssociation ):
+ self.history_dataset_association = dataset
+ elif isinstance( dataset, galaxy.model.LibraryDatasetDatasetAssociation ):
+ self.library_dataset_dataset_association = dataset
+ @property
+ def dataset( self ):
+ if self.history_dataset_association:
+ return self.history_dataset_association
+ elif self.library_dataset_dataset_association:
+ return self.library_dataset_dataset_association
+ return None
+
class Group( object ):
def __init__( self, name = None ):
self.name = name
@@ -162,7 +177,6 @@ class History( object ):
self.user = user
self.datasets = []
self.galaxy_sessions = []
-
def _next_hid( self ):
# TODO: override this with something in the database that ensures
# better integrity
@@ -174,16 +188,14 @@ class History( object ):
if dataset.hid > last_hid:
last_hid = dataset.hid
return last_hid + 1
-
def add_galaxy_session( self, galaxy_session, association=None ):
if association is None:
self.galaxy_sessions.append( GalaxySessionToHistoryAssociation( galaxy_session, self ) )
else:
self.galaxy_sessions.append( association )
-
def add_dataset( self, dataset, parent_id=None, genome_build=None, set_hid = True ):
if isinstance( dataset, Dataset ):
- dataset = HistoryDatasetAssociation( dataset = dataset )
+ dataset = HistoryDatasetAssociation( dataset = dataset, copied_from = dataset )
dataset.flush()
elif not isinstance( dataset, HistoryDatasetAssociation ):
raise TypeError, "You can only add Dataset and HistoryDatasetAssociation instances to a history ( you tried to add %s )." % str( dataset )
@@ -198,31 +210,26 @@ class History( object ):
else:
if set_hid:
dataset.hid = self._next_hid()
+ dataset.history = self
if genome_build not in [None, '?']:
self.genome_build = genome_build
self.datasets.append( dataset )
-
- def copy(self):
- des = History()
+ def copy( self, target_user = None ):
+ if not target_user:
+ target_user = self.user
+ des = History( user = target_user )
des.flush()
des.name = self.name
- des.user_id = self.user_id
for data in self.datasets:
- new_data = data.copy( copy_children = True )
- des.add_dataset( new_data )
+ new_data = data.copy( copy_children = True, target_history = des )
+ des.add_dataset( new_data, set_hid = False )
new_data.flush()
des.hid_counter = self.hid_counter
des.flush()
return des
-
-# class Query( object ):
-# def __init__( self, name=None, state=None, tool_parameters=None, history=None ):
-# self.name = name or "Unnamed query"
-# self.state = state
-# self.tool_parameters = tool_parameters
-# # Relationships
-# self.history = history
-# self.datasets = []
+ @property
+ def activatable_datasets( self ):
+ return [ hda for hda in self.datasets if not hda.dataset.purged ] #this needs to be a list
class UserRoleAssociation( object ):
def __init__( self, user, role ):
@@ -341,7 +348,6 @@ class Dataset( object ):
self.external_filename = external_filename
self._extra_files_path = extra_files_path
self.file_size = file_size
-
def get_file_name( self ):
if not self.external_filename:
assert self.id is not None, "ID must be set before filename used (commit the object)"
@@ -364,15 +370,12 @@ class Dataset( object ):
filename = self.external_filename
# Make filename absolute
return os.path.abspath( filename )
-
def set_file_name ( self, filename ):
if not filename:
self.external_filename = None
else:
self.external_filename = filename
-
file_name = property( get_file_name, set_file_name )
-
@property
def extra_files_path( self ):
if self._extra_files_path:
@@ -384,7 +387,6 @@ class Dataset( object ):
path = os.path.join( os.path.join( self.file_path, *directory_hash_id( self.id ) ), "dataset_%d_files" % self.id )
# Make path absolute
return os.path.abspath( path )
-
def get_size( self ):
"""Returns the size of the data on disk"""
if self.file_size:
@@ -406,7 +408,6 @@ class Dataset( object ):
return self.get_size() > 0
def mark_deleted( self, include_children=True ):
self.deleted = True
-
# FIXME: sqlalchemy will replace this
def _delete(self):
"""Remove the file that corresponds to this data"""
@@ -436,7 +437,7 @@ class DatasetInstance( object ):
self.visible = visible
# Relationships
if not dataset and create_dataset:
- dataset = Dataset()
+ dataset = Dataset( state=Dataset.states.NEW )
dataset.flush()
self.dataset = dataset
self.parent_id = parent_id
@@ -474,7 +475,6 @@ class DatasetInstance( object ):
def get_dbkey( self ):
dbkey = self.metadata.dbkey
if not isinstance(dbkey, list): dbkey = [dbkey]
- #if dbkey in [["?"], [None], []]: dbkey = [self.old_dbkey]
if dbkey in [[None], []]: return "?"
return dbkey[0]
def set_dbkey( self, value ):
@@ -483,10 +483,6 @@ class DatasetInstance( object ):
self.metadata.dbkey = [value]
else:
self.metadata.dbkey = value
- #if isinstance(value, list):
- # self.old_dbkey = value[0]
- #else:
- # self.old_dbkey = value
dbkey = property( get_dbkey, set_dbkey )
def change_datatype( self, new_ext ):
self.clear_associated_files()
@@ -567,7 +563,6 @@ class DatasetInstance( object ):
if self.purged:
return False
return True
-
@property
def source_library_dataset( self ):
def get_source( dataset ):
@@ -599,7 +594,7 @@ class HistoryDatasetAssociation( DatasetInstance ):
self.copied_from_history_dataset_association = copied_from_history_dataset_association
self.copied_from_library_dataset_dataset_association = copied_from_library_dataset_dataset_association
def copy( self, copy_children = False, parent_id = None, target_history = None ):
- des = HistoryDatasetAssociation( hid=self.hid,
+ hda = HistoryDatasetAssociation( hid=self.hid,
name=self.name,
info=self.info,
blurb=self.blurb,
@@ -612,133 +607,71 @@ class HistoryDatasetAssociation( DatasetInstance ):
parent_id=parent_id,
copied_from_history_dataset_association=self,
history = target_history )
- des.flush()
- des.set_size()
- des.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id
+ hda.flush()
+ hda.set_size()
+ hda.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id
if copy_children:
for child in self.children:
- child_copy = child.copy( copy_children = copy_children, parent_id = des.id )
+ child_copy = child.copy( copy_children = copy_children, parent_id = hda.id )
if not self.datatype.copy_safe_peek:
- des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
- des.flush()
- return des
-
- def to_library_dataset_dataset_association( self, parent_id = None, target_folder = None ):
- des = LibraryDatasetDatasetAssociation( name=self.name,
- info=self.info,
- blurb=self.blurb,
- peek=self.peek,
- extension=self.extension,
- dbkey=self.dbkey,
- dataset = self.dataset,
- visible=self.visible,
- deleted=self.deleted,
- parent_id=parent_id,
- copied_from_history_dataset_association = self,
- #folder = target_folder
- )
- des.flush()
- des.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id
- if target_folder:
- new_data = LibraryDataset( library_dataset_dataset_association = des )
- target_folder.add_dataset( new_data )
- new_data.flush()
+ hda.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
+ hda.flush()
+ return hda
+ def to_library_dataset_dataset_association( self, target_folder, parent_id=None ):
+ # Create e new LibraryDataset
+ library_dataset = LibraryDataset( folder=target_folder, name=self.name, info=self.info )
+ library_dataset.flush()
+ ldda = LibraryDatasetDatasetAssociation( name=self.name,
+ info=self.info,
+ blurb=self.blurb,
+ peek=self.peek,
+ extension=self.extension,
+ dbkey=self.dbkey,
+ dataset=self.dataset,
+ library_dataset=library_dataset,
+ visible=self.visible,
+ deleted=self.deleted,
+ parent_id=parent_id,
+ copied_from_history_dataset_association=self )
+ ldda.flush()
+ # Must set metadata after flushed, as MetadataFiles require dataset.id
+ ldda.metadata = self.metadata
+ library_dataset.library_dataset_dataset_association_id = ldda.id
+ library_dataset.flush()
+ target_folder.add_library_dataset( library_dataset, genome_build=ldda.dbkey )
for child in self.children:
- child_copy = child.to_library_dataset_dataset_association( parent_id = des.id )
+ child_copy = child.to_library_dataset_dataset_association( target_folder=target_folder, parent_id=ldda.id )
if not self.datatype.copy_safe_peek:
- des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
- des.flush()
- return des
-
+ ldda.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
+ ldda.flush()
+ return ldda
def clear_associated_files( self, metadata_safe = False, purge = False ):
#metadata_safe = True means to only clear when assoc.metadata_safe == False
for assoc in self.implicitly_converted_datasets:
if not metadata_safe or not assoc.metadata_safe:
assoc.clear( purge = purge )
-class History( object ):
- def __init__( self, id=None, name=None, user=None ):
- self.id = id
- self.name = name or "Unnamed history"
- self.deleted = False
- self.purged = False
- self.genome_build = None
- # Relationships
- self.user = user
- self.datasets = []
- self.galaxy_sessions = []
- def _next_hid( self ):
- # TODO: override this with something in the database that ensures
- # better integrity
- if len( self.datasets ) == 0:
- return 1
- else:
- last_hid = 0
- for dataset in self.datasets:
- if dataset.hid > last_hid:
- last_hid = dataset.hid
- return last_hid + 1
- def add_galaxy_session( self, galaxy_session, association=None ):
- if association is None:
- self.galaxy_sessions.append( GalaxySessionToHistoryAssociation( galaxy_session, self ) )
- else:
- self.galaxy_sessions.append( association )
- def add_dataset( self, dataset, parent_id=None, genome_build=None, set_hid = True ):
- if isinstance( dataset, Dataset ):
- dataset = HistoryDatasetAssociation( dataset = dataset, copied_from = dataset )
- dataset.flush()
- elif not isinstance( dataset, HistoryDatasetAssociation ):
- raise TypeError, "You can only add Dataset and HistoryDatasetAssociation instances to a history."
- if parent_id:
- for data in self.datasets:
- if data.id == parent_id:
- dataset.hid = data.hid
- break
- else:
- if set_hid: dataset.hid = self._next_hid()
- else:
- if set_hid: dataset.hid = self._next_hid()
- dataset.history = self
- if genome_build not in [None, '?']:
- self.genome_build = genome_build
- self.datasets.append( dataset )
- def copy( self, target_user = None ):
- if not target_user:
- target_user = self.user
- des = History( user = target_user )
- des.flush()
- des.name = self.name
- for data in self.datasets:
- new_data = data.copy( copy_children = True, target_history = des )
- des.add_dataset( new_data, set_hid = False )
- new_data.flush()
- des.hid_counter = self.hid_counter
- des.flush()
- return des
-
class Library( object ):
permitted_actions = get_permitted_actions( filter='LIBRARY' )
def __init__( self, name = None, description = None, root_folder = None ):
self.name = name or "Unnamed library"
self.description = description
self.root_folder = root_folder
-
- def get_library_item_info_templates( self, template_list = [] ):
+ def get_library_item_info_templates( self, template_list=[], restrict=False ):
if self.library_info_template_associations:
template_list.extend( [ lita.library_item_info_template for lita in self.library_info_template_associations if lita.library_item_info_template not in template_list ] )
return template_list
class LibraryFolder( object ):
- def __init__( self, name = None, description = None, item_count = 0, order_id = None ):
+ def __init__( self, name=None, description=None, item_count=0, order_id=None ):
self.name = name or "Unnamed folder"
self.description = description
self.item_count = item_count
self.order_id = order_id
self.genome_build = None
- def add_dataset( self, dataset, genome_build=None ):
- #this should create a LibraryDataset if ldda is passed
- dataset.folder_id = self.id
- dataset.order_id = self.item_count
+ def add_library_dataset( self, library_dataset, genome_build=None ):
+ library_dataset.folder_id = self.id
+ library_dataset.order_id = self.item_count
self.item_count += 1
if genome_build not in [None, '?']:
self.genome_build = genome_build
@@ -746,140 +679,123 @@ class LibraryFolder( object ):
folder.parent_id = self.id
folder.order_id = self.item_count
self.item_count += 1
-
- def get_library_item_info_templates( self, template_list = [] ):
+ def get_library_item_info_templates( self, template_list=[], restrict=False ):
+ # If restrict is True, we'll return only those templates directly associated with this Folder
if self.library_folder_info_template_associations:
template_list.extend( [ lfita.library_item_info_template for lfita in self.library_folder_info_template_associations if lfita.library_item_info_template not in template_list ] )
- if self.parent:
+ if restrict not in [ 'True', True ] and self.parent:
self.parent.get_library_item_info_templates( template_list )
- elif self.library_root:
+ elif restrict not in [ 'True', True, 'folder' ] and self.library_root:
for library_root in self.library_root:
library_root.get_library_item_info_templates( template_list )
return template_list
-
@property
def active_components( self ):
return list( self.active_folders ) + list( self.active_datasets )
class LibraryDataset( object ):
- #This class acts as a proxy to the currently selected LDDA
- def __init__( self,
- folder = None,
- order_id = None,
- name = None,
- info = None,
- library_dataset_dataset_association = None,
- create_dataset=False,
- **kwd
- ):
+ # This class acts as a proxy to the currently selected LDDA
+ def __init__( self, folder=None, order_id=None, name=None, info=None, library_dataset_dataset_association=None, **kwd ):
self.folder = folder
self.order_id = order_id
self.name = name
self.info = info
- if create_dataset and not library_dataset_dataset_association:
- #self.flush() #we need to flush self, so that the ldda will have a ld id to point to
- library_dataset_dataset_association = LibraryDatasetDatasetAssociation( name=name, info=info, create_dataset=create_dataset, **kwd )
self.library_dataset_dataset_association = library_dataset_dataset_association
-
- def set_library_dataset_dataset_association( self, dataset ):
- self.library_dataset_dataset_association = dataset
- dataset.library_dataset = self
- dataset.flush()
+ def set_library_dataset_dataset_association( self, ldda ):
+ self.library_dataset_dataset_association = ldda
+ ldda.library_dataset = self
+ ldda.flush()
self.flush()
-
def get_info( self ):
- # Use info from ldfa
- return self.library_dataset_dataset_association.info
+ if self.library_dataset_dataset_association:
+ return self.library_dataset_dataset_association.info
+ elif self._info:
+ return self._info
+ else:
+ return 'no info'
def set_info( self, info ):
self._info = info
info = property( get_info, set_info )
-
def get_name( self ):
- # Use info from ldda
- return self.library_dataset_dataset_association.name
+ if self.library_dataset_dataset_association:
+ return self.library_dataset_dataset_association.name
+ elif self._name:
+ return self._name
+ else:
+ return 'Unnamed dataset'
def set_name( self, name ):
self._name = name
name = property( get_name, set_name )
-
def display_name( self ):
- # Use name from ldda is versioned info is not set
- if self._name:
- return self.datatype.display_name( self )
self.library_dataset_dataset_association.display_name()
-
- def __getattr__( self, name ):
- return getattr( self.library_dataset_dataset_association, name ) #Any nonexistant attributes will be pulled from the ldda
-
- def get_library_item_info_templates( self, template_list = [] ):
+ def get_library_item_info_templates( self, template_list=[], restrict=False ):
+ # If restrict is True, we'll return only those templates directly associated with this LibraryDataset
if self.library_dataset_info_template_associations:
template_list.extend( [ ldita.library_item_info_template for ldita in self.library_dataset_info_template_associations if ldita.library_item_info_template not in template_list ] )
- self.folder.get_library_item_info_templates( template_list )
+ if restrict not in [ 'True', True ]:
+ self.folder.get_library_item_info_templates( template_list, restrict )
return template_list
class LibraryDatasetDatasetAssociation( DatasetInstance ):
- def __init__( self,
- copied_from_history_dataset_association = None,
- copied_from_library_dataset_dataset_association = None,
- library_dataset = None,
- **kwd ):
+ def __init__( self, copied_from_history_dataset_association=None, copied_from_library_dataset_dataset_association=None, library_dataset=None, **kwd ):
DatasetInstance.__init__( self, **kwd )
- self.library_dataset = library_dataset
self.copied_from_history_dataset_association = copied_from_history_dataset_association
self.copied_from_library_dataset_dataset_association = copied_from_library_dataset_dataset_association
- def to_history_dataset_association( self, parent_id = None, target_history = None ):
- if target_history:
- hid = target_history._next_hid()
- else:
- hid = None
- des = HistoryDatasetAssociation( name=self.name,
- info=self.info,
+ self.library_dataset = library_dataset
+ def to_history_dataset_association( self, target_history, parent_id=None ):
+ hid = target_history._next_hid()
+ hda = HistoryDatasetAssociation( name=self.name,
+ info=self.info,
blurb=self.blurb,
peek=self.peek,
extension=self.extension,
dbkey=self.dbkey,
- dataset = self.dataset,
+ dataset=self.dataset,
visible=self.visible,
deleted=self.deleted,
parent_id=parent_id,
- copied_from_library_dataset_dataset_association = self,
- history = target_history,
- hid = hid )
- des.flush()
- des.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id
+ copied_from_library_dataset_dataset_association=self,
+ history=target_history,
+ hid=hid )
+ hda.flush()
+ hda.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id
for child in self.children:
- child_copy = child.to_history_dataset_association( parent_id = des.id )
+ child_copy = child.to_history_dataset_association( target_history=target_history, parent_id=hda.id )
if not self.datatype.copy_safe_peek:
- des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
- des.flush()
- return des
+ hda.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
+ hda.flush()
+ return hda
def copy( self, copy_children = False, parent_id = None, target_folder = None ):
- des = LibraryDatasetDatasetAssociation( name=self.name,
- info=self.info,
- blurb=self.blurb,
- peek=self.peek,
- extension=self.extension,
- dbkey=self.dbkey,
- dataset = self.dataset,
- visible=self.visible,
- deleted=self.deleted,
- parent_id=parent_id,
- copied_from_library_dataset_dataset_association = self,
- folder = target_folder )
- des.flush()
- des.metadata = self.metadata #need to set after flushed, as MetadataFiles require dataset.id
+ ldda = LibraryDatasetDatasetAssociation( name=self.name,
+ info=self.info,
+ blurb=self.blurb,
+ peek=self.peek,
+ extension=self.extension,
+ dbkey=self.dbkey,
+ dataset=self.dataset,
+ visible=self.visible,
+ deleted=self.deleted,
+ parent_id=parent_id,
+ copied_from_library_dataset_dataset_association=self,
+ folder=target_folder )
+ ldda.flush()
+ # Need to set after flushed, as MetadataFiles require dataset.id
+ ldda.metadata = self.metadata
if copy_children:
for child in self.children:
- child_copy = child.copy( copy_children = copy_children, parent_id = des.id )
+ child_copy = child.copy( copy_children = copy_children, parent_id = ldda.id )
if not self.datatype.copy_safe_peek:
- des.set_peek() #in some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
- des.flush()
- return des
+ # In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
+ ldda.set_peek()
+ ldda.flush()
+ return ldda
def clear_associated_files( self, metadata_safe = False, purge = False ):
return
- def get_library_item_info_templates( self, template_list = [] ):
+ def get_library_item_info_templates( self, template_list=[], restrict=False ):
+ # If restrict is True, we'll return only those templates directly associated with this LibraryDatasetDatasetAssociation
if self.library_dataset_dataset_info_template_associations:
template_list.extend( [ lddita.library_item_info_template for lddita in self.library_dataset_dataset_info_template_associations if lddita.library_item_info_template not in template_list ] )
- self.library_dataset.get_library_item_info_templates( template_list )
+ self.library_dataset.get_library_item_info_templates( template_list, restrict )
return template_list
class LibraryInfoTemplateAssociation( object ):
@@ -909,45 +825,38 @@ class LibraryItemInfoTemplate( object ):
new_elem.flush()
return new_elem
- """class InfoField( object ):
- def __init__( self, name, description, type ):
- self.name = name
- self.description = description
- self.type = type
- class StringInfoField( InfoField ):
- def __init__( self, name, description, type = 'string' ):
- InfoField.__init__( self, name, description, type )
- def __init__( self, name='unnamed', contents=None ):
- self.contents = contents
- """
class LibraryItemInfoTemplateElement( object ):
pass
class LibraryInfoAssociation( object ):
- def set_library_item( self, library_item ):
+ def set_library_item( self, library_item, user ):
if isinstance( library_item, Library ):
self.library = library_item
+ self.user = user
else:
raise "Invalid Library specified: %s" % library_item.__class__.__name__
class LibraryFolderInfoAssociation( object ):
- def set_library_item( self, library_item ):
+ def set_library_item( self, library_item, user ):
if isinstance( library_item, LibraryFolder ):
self.folder = library_item
+ self.user = user
else:
raise "Invalid Library specified: %s" % library_item.__class__.__name__
class LibraryDatasetInfoAssociation( object ):
- def set_library_item( self, library_item ):
+ def set_library_item( self, library_item, user ):
if isinstance( library_item, LibraryDataset ):
self.library_dataset = library_item
+ self.user = user
else:
raise "Invalid Library specified: %s" % library_item.__class__.__name__
class LibraryDatasetDatasetInfoAssociation( object ):
- def set_library_item( self, library_item ):
+ def set_library_item( self, library_item, user ):
if isinstance( library_item, LibraryDatasetDatasetAssociation ):
self.library_dataset_dataset_association = library_item
+ self.user = user
else:
raise "Invalid Library specified: %s" % library_item.__class__.__name__
@@ -960,16 +869,6 @@ class LibraryItemInfo( object ):
class LibraryItemInfoElement( object ):
pass
-
-# class Query( object ):
-# def __init__( self, name=None, state=None, tool_parameters=None, history=None ):
-# self.name = name or "Unnamed query"
-# self.state = state
-# self.tool_parameters = tool_parameters
-# # Relationships
-# self.history = history
-# self.datasets = []
-
class ValidationError( object ):
def __init__( self, message=None, err_type=None, attributes=None ):
diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py
index 3a128e65cb0..d416e00acd4 100644
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -58,13 +58,6 @@ History.table = Table( "history", metadata,
Column( "purged", Boolean, index=True, default=False ),
Column( "genome_build", TrimmedString( 40 ) ) )
-# model.Query.table = Table( "query", engine,
-# Column( "id", Integer, primary_key=True),
-# Column( "history_id", Integer, ForeignKey( "history.id" ) ),
-# Column( "name", String( 255 ) ),
-# Column( "state", String( 64 ) ),
-# Column( "tool_parameters", Pickle() ) )
-
HistoryDatasetAssociation.table = Table( "history_dataset_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ),
@@ -228,10 +221,8 @@ LibraryDataset.table = Table( "library_dataset", metadata,
Column( "update_time", DateTime, default=now, onupdate=now ),
Column( "name", TrimmedString( 255 ), key="_name" ), #when not None/null this will supercede display in library (but not when imported into user's history?)
Column( "info", TrimmedString( 255 ), key="_info" ), #when not None/null this will supercede display in library (but not when imported into user's history?)
- Column( "deleted", Boolean, index=True, default=False ),
- )
+ Column( "deleted", Boolean, index=True, default=False ) )
-#this should be renamed, no longer an association between dataset and folder
LibraryDatasetDatasetAssociation.table = Table( "library_dataset_dataset_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "library_dataset_id", Integer, ForeignKey( "library_dataset.id" ), index=True ),
@@ -329,7 +320,7 @@ LibraryItemInfoElement.table = Table( "library_item_info_element", metadata,
Column( "id", Integer, primary_key=True ),
Column( "create_time", DateTime, default=now ),
Column( "update_time", DateTime, default=now, onupdate=now ),
- Column( "contents", TEXT ),
+ Column( "contents", JSONType() ),
Column( "library_item_info_id", Integer, ForeignKey( "library_item_info.id" ), index=True ),
Column( "library_item_info_template_element_id", Integer, ForeignKey( "library_item_info_template_element.id" ), index=True ) )
@@ -411,6 +402,17 @@ JobToOutputDatasetAssociation.table = Table( "job_to_output_dataset", metadata,
Column( "dataset_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
Column( "name", String(255) ) )
+JobExternalOutputMetadata.table = Table( "job_external_output_metadata", metadata,
+ Column( "id", Integer, primary_key=True ),
+ Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ),
+ Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True, nullable=True ),
+ Column( "library_dataset_dataset_association_id", Integer, ForeignKey( "library_dataset_dataset_association.id" ), index=True, nullable=True ),
+ Column( "filename_in", String( 255 ) ),
+ Column( "filename_out", String( 255 ) ),
+ Column( "filename_results_code", String( 255 ) ),
+ Column( "filename_kwds", String( 255 ) ),
+ Column( "job_runner_external_pid", String( 255 ) ) )
+
Event.table = Table( "event", metadata,
Column( "id", Integer, primary_key=True ),
Column( "create_time", DateTime, default=now ),
@@ -567,8 +569,7 @@ assign_mapper( context, ImplicitlyConvertedDatasetAssociation, ImplicitlyConvert
assign_mapper( context, History, History.table,
properties=dict( galaxy_sessions=relation( GalaxySessionToHistoryAssociation ),
datasets=relation( HistoryDatasetAssociation, backref="history", order_by=asc(HistoryDatasetAssociation.table.c.hid) ),
- active_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( HistoryDatasetAssociation.table.c.deleted ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), lazy=False, viewonly=True ),
- activatable_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( Dataset.table.c.purged ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), lazy=True, viewonly=True )
+ active_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( HistoryDatasetAssociation.table.c.deleted ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), lazy=False, viewonly=True )
) )
assign_mapper( context, User, User.table,
@@ -790,12 +791,18 @@ assign_mapper( context, JobToOutputDatasetAssociation, JobToOutputDatasetAssocia
assign_mapper( context, JobParameter, JobParameter.table )
+assign_mapper( context, JobExternalOutputMetadata, JobExternalOutputMetadata.table,
+ properties=dict( job = relation( Job ),
+ history_dataset_association = relation( HistoryDatasetAssociation, lazy = False ),
+ library_dataset_dataset_association = relation( LibraryDatasetDatasetAssociation, lazy = False ) ) )
+
assign_mapper( context, Job, Job.table,
properties=dict( galaxy_session=relation( GalaxySession ),
history=relation( History ),
parameters=relation( JobParameter, lazy=False ),
input_datasets=relation( JobToInputDatasetAssociation, lazy=False ),
- output_datasets=relation( JobToOutputDatasetAssociation, lazy=False ) ) )
+ output_datasets=relation( JobToOutputDatasetAssociation, lazy=False ),
+ external_output_metadata = relation( JobExternalOutputMetadata, lazy = False ) ) )
assign_mapper( context, Event, Event.table,
properties=dict( history=relation( History ),
diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py
index 3b321c7313a..6bd82f48e64 100644
--- a/lib/galaxy/security/__init__.py
+++ b/lib/galaxy/security/__init__.py
@@ -219,13 +219,6 @@ class GalaxyRBACAgent( RBACAgent ):
dup.flush()
if history:
for history in user.active_histories:
- """
- TODO: instead of user.active_histories, a faster approach would be the following, but we need the
- history.activatable_datasets mapper corrected so that eagerload can be used.
- histories = app.model.History.filter( and_( app.model.History.table.c.user_id==user.id,
- app.model.History.table.c.purged==False ) ) \
- .options( eagerload( 'activatable_datasets' ) ).all()
- """
self.history_set_default_permissions( history, permissions=permissions, dataset=dataset, bypass_manage_permission=bypass_manage_permission )
def user_get_default_permissions( self, user ):
permissions = {}
@@ -352,7 +345,6 @@ class GalaxyRBACAgent( RBACAgent ):
self.set_dataset_permission( dataset, { self.permitted_actions.DATASET_ACCESS : [ sharing_role ] } )
def set_all_library_permissions( self, library_item, permissions={} ):
# Set new permissions on library_item, eliminating all current permissions
- # TODO: Make sure the following should still be library_item.actions
for role_assoc in library_item.actions:
role_assoc.delete()
role_assoc.flush()
@@ -364,6 +356,19 @@ class GalaxyRBACAgent( RBACAgent ):
action = action.action
for role_assoc in [ permission_class( action, library_item, role ) for role in roles ]:
role_assoc.flush()
+ def get_library_dataset_permissions( self, library_dataset ):
+ # Permissions will always be the same for LibraryDatasets and associated
+ # LibraryDatasetDatasetAssociations
+ if isinstance( library_dataset, self.model.LibraryDatasetDatasetAssociation ):
+ library_dataset = library_dataset.library_dataset
+ permissions = {}
+ for library_dataset_permission in library_dataset.actions:
+ action = self.get_action( library_dataset_permission.action )
+ if action in permissions:
+ permissions[ action ].append( library_dataset_permission.role )
+ else:
+ permissions[ action ] = [ library_dataset_permission.role ]
+ return permissions
def copy_library_permissions( self, source_library_item, target_library_item, user=None ):
# Copy all permissions from source
permissions = {}
@@ -452,16 +457,16 @@ class GalaxyRBACAgent( RBACAgent ):
"""
if isinstance( entry, self.model.Library ):
return self.check_folder_contents( user, entry.root_folder )
- elif isinstance( entry, self.model.LibraryDatasetDatasetAssociation ):
- return self.allow_action( user, self.permitted_actions.DATASET_ACCESS, dataset=entry )
elif isinstance( entry, self.model.LibraryFolder ):
- for dataset in entry.active_datasets:
- if self.allow_action( user, self.permitted_actions.DATASET_ACCESS, dataset=dataset ):
+ for library_dataset in entry.active_datasets:
+ if self.allow_action( user, self.permitted_actions.DATASET_ACCESS, dataset=library_dataset.library_dataset_dataset_association.dataset ):
return True
for folder in entry.active_folders:
if self.check_folder_contents( user, folder ):
return True
return False
+ elif isinstance( entry, self.model.LibraryDatasetDatasetAssociation ):
+ return self.allow_action( user, self.permitted_actions.DATASET_ACCESS, dataset=entry.dataset )
else:
raise 'Passed an illegal object to check_folder_contents: %s' % type( entry )
diff --git a/lib/galaxy/tools/actions/upload.py b/lib/galaxy/tools/actions/upload.py
index f0a05835bf7..8d293dcd5af 100644
--- a/lib/galaxy/tools/actions/upload.py
+++ b/lib/galaxy/tools/actions/upload.py
@@ -83,11 +83,13 @@ class UploadToolAction( object ):
else:
INFO = "uploaded url"
url_paste = url_paste.replace( '\r', '' ).split( '\n' )
+ name_set_from_line = False #if we are setting the name from the line, it needs to be the line that creates that dataset
for line in url_paste:
line = line.rstrip( '\r\n' )
if line:
- if not NAME:
+ if not NAME or name_set_from_line:
NAME = line
+ name_set_from_line = True
try:
temp_name = sniff.stream_to_file( urllib.urlopen( line ), prefix='url_paste' )
except Exception, e:
@@ -126,7 +128,8 @@ class UploadToolAction( object ):
return self.upload_empty( trans, job, "Empty file error:", "you attempted to upload an empty file." )
elif len( data_list ) < 1:
return self.upload_empty( trans, job, "No data error:", "either you pasted no data, the url you specified is invalid, or you have not specified a file." )
- hda = data_list[0]
+ #if we could make a 'real' job here, then metadata could be set before job.finish() is called
+ hda = data_list[0] #only our first hda is being added as input for the job, why?
job.state = trans.app.model.Job.states.OK
file_size_str = datatypes.data.nice_size( hda.dataset.file_size )
job.info = "%s, size: %s" % ( hda.info, file_size_str )
@@ -229,7 +232,6 @@ class UploadToolAction( object ):
else:
self.line_count = sniff.convert_newlines( temp_name )
if file_type == 'auto':
- log.debug("In upload, in if file_type == 'auto':")
ext = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order )
else:
ext = file_type
diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py
index 7a64793cdf5..b0adadbedff 100644
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -387,6 +387,41 @@ def read_build_sites(filename):
print "ERROR: Unable to read builds for site file %s" %filename
return build_sites
+def relpath( path, start = None ):
+ """Return a relative version of a path"""
+ #modified from python 2.6.1 source code
+
+ #version 2.6+ has it built in, we'll use the 'official' copy
+ if sys.version_info[:2] >= ( 2, 6 ):
+ if start is not None:
+ return os.path.relpath( path, start )
+ return os.path.relpath( path )
+
+ #we need to initialize some local parameters
+ curdir = os.curdir
+ pardir = os.pardir
+ sep = os.sep
+ commonprefix = os.path.commonprefix
+ join = os.path.join
+ if start is None:
+ start = curdir
+
+ #below is the unedited (but formated) relpath() from posixpath.py of 2.6.1
+ #this will likely not function properly on non-posix systems, i.e. windows
+ if not path:
+ raise ValueError( "no path specified" )
+
+ start_list = os.path.abspath( start ).split( sep )
+ path_list = os.path.abspath( path ).split( sep )
+
+ # Work out how much of the filepath is shared by start and path.
+ i = len( commonprefix( [ start_list, path_list ] ) )
+
+ rel_list = [ pardir ] * ( len( start_list )- i ) + path_list[ i: ]
+ if not rel_list:
+ return curdir
+ return join( *rel_list )
+
galaxy_root_path = os.path.join(__path__[0], "..","..","..")
dbnames = read_dbnames( os.path.join( galaxy_root_path, "tool-data", "shared", "ucsc", "builds.txt" ) ) #this list is used in edit attributes and the upload tool
ucsc_build_sites = read_build_sites( os.path.join( galaxy_root_path, "tool-data", "shared", "ucsc", "ucsc_build_sites.txt" ) ) #this list is used in history.tmpl
diff --git a/lib/galaxy/web/controllers/admin.py b/lib/galaxy/web/controllers/admin.py
index ed0dc5431ff..73249388e3f 100644
--- a/lib/galaxy/web/controllers/admin.py
+++ b/lib/galaxy/web/controllers/admin.py
@@ -1,7 +1,6 @@
import shutil, StringIO, operator, urllib, gzip, tempfile
from galaxy import util, datatypes
from galaxy.web.base.controller import *
-from galaxy.web.controllers.dataset import upload_dataset
from galaxy.model.orm import *
import logging
@@ -856,9 +855,10 @@ class Admin( BaseController ):
root_folder.flush()
library.root_folder = root_folder
library.flush()
- msg = 'The new library named %s has been created' % library.name
+ msg = "The new library named '%s' has been created" % library.name
return trans.response.send_redirect( web.url_for( controller='admin',
- action='browse_libraries',
+ action='browse_library',
+ id=library.id,
msg=util.sanitize_text( msg ),
messagetype='done' ) )
return trans.fill_template( '/admin/library/new_library.mako', msg=msg, messagetype=messagetype )
@@ -869,13 +869,13 @@ class Admin( BaseController ):
new_description = util.restore_text( params.description )
if not new_name:
msg = 'Enter a valid name'
- return trans.fill_template( '/admin/library/library_info.mako', library=library, msg=msg, messagetype='error' )
+ return trans.fill_template( '/admin/library/library_info.mako',
+ library=library,
+ restrict=params.get( 'restrict', False ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype='error' )
else:
- if params.get( 'root_folder', False ):
- root_folder = library.root_folder
- root_folder.name = new_name
- root_folder.flush()
- root_folder.refresh()
library.name = new_name
library.description = new_description
library.flush()
@@ -886,21 +886,34 @@ class Admin( BaseController ):
information=True,
msg=util.sanitize_text( msg ),
messagetype='done' ) )
- return trans.fill_template( '/admin/library/library_info.mako', library=library, msg=msg, messagetype=messagetype )
+ return trans.fill_template( '/admin/library/library_info.mako',
+ library=library,
+ restrict=params.get( 'restrict', False ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
elif action == 'delete':
# TODO: need to revamp the way we delete libraries, folders and contained LibraryDatasets.
def delete_folder( library_folder ):
- for folder in library_folder.active_folders:
+ library_folder.refresh()
+ for folder in library_folder.folders:
delete_folder( folder )
- for ldda in library_folder.active_datasets:
- # We don't set ldda.dataset.deleted to True here because the cleanup_dataset script
- # will eventually remove it from disk. The purge_library method below sets the dataset
- # to deleted. This allows for the library to be undeleted ( before it is purged ),
- # restoring all of its contents.
- ldda.deleted = True
- ldda.flush()
+ for library_dataset in library_folder.datasets:
+ library_dataset.refresh()
+ ldda = library_dataset.library_dataset_dataset_association
+ if ldda:
+ ldda.refresh()
+ # We don't set ldda.dataset.deleted to True here because the cleanup_dataset script
+ # will eventually remove it from disk. The purge_library method below sets the dataset
+ # to deleted. This allows for the library to be undeleted ( before it is purged ),
+ # restoring all of its contents.
+ ldda.deleted = True
+ ldda.flush()
+ library_dataset.deleted = True
+ library_dataset.flush()
library_folder.deleted = True
library_folder.flush()
+ library.refresh()
delete_folder( library.root_folder )
library.deleted = True
library.flush()
@@ -944,10 +957,18 @@ class Admin( BaseController ):
library = trans.app.model.Library.get( int( params.id ) )
def undelete_folder( library_folder ):
for folder in library_folder.folders:
+ folder.refresh()
undelete_folder( folder )
- for ldda in library_folder.datasets:
- ldda.deleted = False
- ldda.flush()
+ library_folder.refresh()
+ for library_dataset in library_folder.datasets:
+ library_dataset.refresh()
+ ldda = library_dataset.library_dataset_dataset_association
+ if ldda:
+ ldda.refresh()
+ ldda.deleted = False
+ ldda.flush()
+ library_dataset.deleted = False
+ library_dataset.flush()
library_folder.deleted = False
library_folder.flush()
undelete_folder( library.root_folder )
@@ -963,18 +984,25 @@ class Admin( BaseController ):
def purge_folder( library_folder ):
for lf in library_folder.folders:
purge_folder( lf )
- for ldda in library_folder.datasets:
- ldda.refresh()
- dataset = ldda.dataset
- dataset.refresh()
- # If the dataset is not associated with any additional undeleted folders, then we can delete it.
- # We don't set dataset.purged to True here because the cleanup_datasets script will do that for
- # us, as well as removing the file from disk.
- if not dataset.deleted and len( dataset.active_library_associations ) <= 1: # This is our current ldda
+ library_folder.refresh()
+ for library_dataset in library_folder.datasets:
+ library_dataset.refresh()
+ ldda = library_dataset.library_dataset_dataset_association
+ if ldda:
+ ldda.refresh()
+ dataset = ldda.dataset
+ dataset.refresh()
+ # If the dataset is not associated with any additional undeleted folders, then we can delete it.
+ # We don't set dataset.purged to True here because the cleanup_datasets script will do that for
+ # us, as well as removing the file from disk.
+ #if not dataset.deleted and len( dataset.active_library_associations ) <= 1: # This is our current ldda
dataset.deleted = True
dataset.flush()
- ldda.deleted = True
- ldda.flush()
+ ldda.deleted = True
+ ldda.flush()
+ library_dataset.deleted = True
+ library_dataset.flush()
+ library_folder.deleted = True
library_folder.purged = True
library_folder.flush()
purge_folder( library.root_folder )
@@ -1000,7 +1028,7 @@ class Admin( BaseController ):
else:
# 'information' will be the default
action = 'information'
- folder = trans.app.model.LibraryFolder.get( id )
+ folder = trans.app.model.LibraryFolder.get( int( id ) )
if not folder:
msg = "Invalid folder specified, id: %s" % str( id )
return trans.response.send_redirect( web.url_for( controller='admin',
@@ -1010,8 +1038,6 @@ class Admin( BaseController ):
messagetype='error' ) )
if action == 'new':
if params.new == 'submitted':
- # Create the new folder, then return manage_folder template for new folder.
- # New folders default to having the same permissions as their parent folder
new_folder = trans.app.model.LibraryFolder( name=util.restore_text( params.name ),
description=util.restore_text( params.description ) )
# We are associating the last used genome build with folders, so we will always
@@ -1020,13 +1046,12 @@ class Admin( BaseController ):
new_folder.genome_build = util.dbnames.default_value
folder.add_folder( new_folder )
new_folder.flush()
+ # New folders default to having the same permissions as their parent folder
trans.app.security_agent.copy_library_permissions( folder, new_folder, user=trans.get_user() )
msg = "New folder named '%s' has been added to the library" % new_folder.name
return trans.response.send_redirect( web.url_for( controller='admin',
- action='folder',
- id=id,
- library_id=library_id,
- information=True,
+ action='browse_library',
+ id=library_id,
msg=util.sanitize_text( msg ),
messagetype='done' ) )
return trans.fill_template( '/admin/library/new_folder.mako',
@@ -1044,6 +1069,8 @@ class Admin( BaseController ):
return trans.fill_template( '/admin/library/folder_info.mako',
folder=folder,
library_id=library_id,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
msg=msg,
messagetype='error' )
else:
@@ -1061,6 +1088,8 @@ class Admin( BaseController ):
return trans.fill_template( '/admin/library/folder_info.mako',
folder=folder,
library_id=library_id,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
msg=msg,
messagetype=messagetype )
elif action == 'delete':
@@ -1104,10 +1133,17 @@ class Admin( BaseController ):
messagetype=messagetype )
@web.expose
@web.require_admin
- def library_dataset( self, trans, id, name=None, info=None, library_id=None, **kwd ):
+ def library_dataset( self, trans, id, library_id, **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
+ if params.get( 'permissions', False ):
+ action = 'permissions'
+ elif params.get( 'versions', False ):
+ action = 'versions'
+ else:
+ # 'information' will be the default
+ action = 'information'
library_dataset = trans.app.model.LibraryDataset.get( id )
if not library_dataset:
msg = "Invalid library dataset specified, id: %s" %str( id )
@@ -1116,33 +1152,61 @@ class Admin( BaseController ):
id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- if params.get( 'edit_attributes_button', False ):
- library_dataset.name = name
- library_dataset.info = info
- msg = 'Attributes updated for library dataset %s' % library_dataset.name
- elif params.get( 'change_version', False ):
- target_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( kwd.get( 'set_lda_id' ) )
- library_dataset.library_dataset_dataset_association = target_lda
- trans.app.model.flush()
- msg = 'The current version of this library dataset has been updated to be %s' % target_lda.name
- elif params.get( 'update_roles_button', False ):
- # The user clicked the Save button on the 'Edit permissions and role associations' form
- permissions = {}
- for k, v in trans.app.model.Library.permitted_actions.items():
- in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ]
- permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
- trans.app.security_agent.set_all_library_permissions( library_dataset, permissions )
- library_dataset.refresh()
- msg = 'Permissions and roles have been updated for library dataset %s' % library_dataset.name
- return trans.fill_template( "/admin/library/manage_library_dataset.mako",
- library_dataset=library_dataset,
- library_id=library_id,
- msg=msg,
- messagetype=messagetype )
+ if action == 'information':
+ if params.get( 'edit_attributes_button', False ):
+ old_name = library_dataset.name
+ new_name = util.restore_text( params.get( 'name', '' ) )
+ new_info = util.restore_text( params.get( 'info', '' ) )
+ if not new_name:
+ msg = 'Enter a valid name'
+ messagetype = 'error'
+ else:
+ library_dataset.name = new_name
+ library_dataset.info = new_info
+ library_dataset.flush()
+ msg = "Dataset '%s' has been renamed to '%s'" % ( old_name, new_name )
+ messagetype = 'done'
+ return trans.fill_template( '/admin/library/library_dataset_info.mako',
+ library_dataset=library_dataset,
+ library_id=library_id,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
+ elif action == 'versions':
+ if params.get( 'change_version_button', False ):
+ target_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( kwd.get( 'set_lda_id' ) )
+ library_dataset.library_dataset_dataset_association = target_lda
+ trans.app.model.flush()
+ msg = 'The current version of this library dataset has been updated to be %s' % target_lda.name
+ return trans.fill_template( '/admin/library/library_dataset_versions.mako',
+ library_dataset=library_dataset,
+ library_id=library_id,
+ msg=msg,
+ messagetype=messagetype )
+ elif action == 'permissions':
+ if params.get( 'update_roles_button', False ):
+ # The user clicked the Save button on the 'Edit permissions and role associations' form
+ permissions = {}
+ for k, v in trans.app.model.Library.permitted_actions.items():
+ in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ]
+ permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
+ # Set the LIBRARY permissions on the LibraryDataset
+ # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions
+ trans.app.security_agent.set_all_library_permissions( library_dataset, permissions )
+ library_dataset.refresh()
+ # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation
+ trans.app.security_agent.set_all_library_permissions( library_dataset.library_dataset_dataset_association, permissions )
+ library_dataset.library_dataset_dataset_association.refresh()
+ msg = 'Permissions and roles have been updated for library dataset %s' % library_dataset.name
+ return trans.fill_template( '/admin/library/library_dataset_permissions.mako',
+ library_dataset=library_dataset,
+ library_id=library_id,
+ msg=msg,
+ messagetype=messagetype )
@web.expose
@web.require_admin
- def library_dataset_dataset_association( self, trans, id=None, name="Unnamed", info='no info',
- extension=None, folder_id=None, library_id=None, **kwd ):
+ def library_dataset_dataset_association( self, trans, library_id, folder_id, id=None, **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
@@ -1151,41 +1215,48 @@ class Admin( BaseController ):
last_used_build = dbkey[0]
else:
last_used_build = dbkey
- if folder_id and not last_used_build:
+ if folder_id:
folder = trans.app.model.LibraryFolder.get( folder_id )
+ else:
+ folder = None
+ if folder and not last_used_build:
last_used_build = folder.genome_build
- data_files = []
try:
replace_dataset = trans.app.model.LibraryDataset.get( params.get( 'replace_id', None ) )
except:
replace_dataset = None
+ # Let's not overwrite the imported datatypes module with the variable datatypes?
+ # The built-in 'id' is overwritten in lots of places as well
+ ldatatypes = [ x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys() ]
+ ldatatypes.sort()
if params.get( 'new_dataset_button', False ):
- created_ldda_ids = upload_dataset( trans,
- controller='admin',
- folder_id=folder_id,
- replace_dataset=replace_dataset,
- **kwd )
+ created_ldda_ids = trans.webapp.controllers[ 'library_dataset' ].upload_dataset( trans,
+ controller='admin',
+ library_id=library_id,
+ folder_id=folder_id,
+ replace_dataset=replace_dataset,
+ **kwd )
if created_ldda_ids:
total_added = len( created_ldda_ids.split( ',' ) )
- msg = "%i new datasets added to the library ( each is selected below ). " % total_added
- msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
- trans.response.send_redirect( web.url_for( controller='admin',
- action='browse_library',
- id=library_id,
- created_ldda_ids=created_ldda_ids,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
+ if replace_dataset:
+ msg = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset.name, folder.name )
+ else:
+ msg = "Added %d datasets to the library folder '%s' ( each is selected ). " % ( total_added, folder.name )
+ msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
+ messagetype='done'
else:
msg = "Upload failed"
- trans.response.send_redirect( web.url_for( controller='admin',
- action='browse_library',
- id=library_id,
- created_ldda_ids=created_ldda_ids,
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
+ messagetype='error'
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_library',
+ id=library_id,
+ created_ldda_ids=created_ldda_ids,
+ msg=util.sanitize_text( msg ),
+ messagetype=messagetype ) )
elif not id or replace_dataset:
- # No dataset(s) specified, so display the upload form.
- # Send list of data formats to the form so the "extension" select list can be populated dynamically
+ upload_option = params.get( 'upload_option', 'upload_file' )
+ # No dataset(s) specified, so display the upload form. Send list of data formats to the form
+ # so the "extension" select list can be populated dynamically
file_formats = trans.app.datatypes_registry.upload_file_formats
# Send list of genome builds to the form so the "dbkey" select list can be populated dynamically
def get_dbkey_options( last_used_build ):
@@ -1194,10 +1265,11 @@ class Admin( BaseController ):
dbkeys = get_dbkey_options( last_used_build )
# Send list of roles to the form so the dataset can be associated with 1 or more of them.
roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all()
- # Send the current history to enable importing datasets from history to library
+ # Send the current history to the form to enable importing datasets from history to library
history = trans.get_history()
history.refresh()
return trans.fill_template( '/admin/library/new_dataset.mako',
+ upload_option=upload_option,
library_id=library_id,
folder_id=folder_id,
file_formats=file_formats,
@@ -1236,60 +1308,74 @@ class Admin( BaseController ):
for k, v in trans.app.model.Dataset.permitted_actions.items():
in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ]
permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
+ # Set the DATASET permissions on the Dataset
trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, permissions )
- # Set/display library security info
+ ldda.dataset.refresh()
permissions = {}
for k, v in trans.app.model.Library.permitted_actions.items():
in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ]
permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
+ # Set the LIBRARY permissions on the LibraryDataset
+ # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions
+ trans.app.security_agent.set_all_library_permissions( ldda.library_dataset, permissions )
+ ldda.library_dataset.refresh()
+ # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation
trans.app.security_agent.set_all_library_permissions( ldda, permissions )
- ldda.dataset.refresh()
+ ldda.refresh()
msg = "Permissions updated for dataset '%s'" % ldda.name
- return trans.response.send_redirect( web.url_for( controller='admin',
- action='library_dataset_dataset_association',
- id=id,
- permissions=True,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
- return trans.fill_template( '/admin/library/ldda_permissions.mako', ldda=ldda, library_id=library_id, msg=msg, messagetype=messagetype )
+ return trans.fill_template( '/admin/library/ldda_permissions.mako',
+ ldda=ldda,
+ library_id=library_id,
+ msg=msg,
+ messagetype=messagetype )
elif action == 'information':
if params.get( 'change', False ):
# The user clicked the Save button on the 'Change data type' form
trans.app.datatypes_registry.change_datatype( ldda, params.datatype )
trans.app.model.flush()
- msg = "Data type changed for dataset '%s'" % ldda.name
- return trans.response.send_redirect( web.url_for( controller='admin',
- action='library_dataset_dataset_association',
- id=id,
- library_id=library_id,
- information=True,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
+ msg = "Data type changed for library dataset '%s'" % ldda.name
+ return trans.fill_template( "/admin/library/ldda_info.mako",
+ ldda=ldda,
+ library_id=library_id,
+ datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
elif params.get( 'save', False ):
# The user clicked the Save button on the 'Edit Attributes' form
- ldda.name = name
- ldda.info = info
- # The following for loop will save all metadata_spec items
- for name, spec in ldda.datatype.metadata_spec.items():
- if spec.get("readonly"):
- continue
- optional = params.get( "is_" + name, None )
- if optional and optional == 'true':
- # optional element... == 'true' actually means it is NOT checked (and therefore ommitted)
- setattr( ldda.metadata, name, None )
- else:
- setattr( ldda.metadata, name, spec.unwrap( params.get ( name, None ) ) )
- ldda.metadata.dbkey = dbkey
- ldda.datatype.after_edit( ldda )
- trans.app.model.flush()
- msg = 'Attributes updated for dataset %s' % ldda.name
- return trans.response.send_redirect( web.url_for( controller='admin',
- action='library_dataset_dataset_association',
- id=id,
- library_id=library_id,
- information=True,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
+ old_name = ldda.name
+ new_name = util.restore_text( params.get( 'name', '' ) )
+ new_info = util.restore_text( params.get( 'info', '' ) )
+ if not new_name:
+ msg = 'Enter a valid name'
+ messagetype = 'error'
+ else:
+ ldda.name = new_name
+ ldda.info = new_info
+ # The following for loop will save all metadata_spec items
+ for name, spec in ldda.datatype.metadata_spec.items():
+ if spec.get("readonly"):
+ continue
+ optional = params.get( "is_" + name, None )
+ if optional and optional == 'true':
+ # optional element... == 'true' actually means it is NOT checked (and therefore ommitted)
+ setattr( ldda.metadata, name, None )
+ else:
+ setattr( ldda.metadata, name, spec.unwrap( params.get ( name, None ) ) )
+ ldda.metadata.dbkey = dbkey
+ ldda.datatype.after_edit( ldda )
+ trans.app.model.flush()
+ msg = 'Attributes updated for library dataset %s' % ldda.name
+ messagetype = 'done'
+ return trans.fill_template( "/admin/library/ldda_info.mako",
+ ldda=ldda,
+ library_id=library_id,
+ datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
elif params.get( 'detect', False ):
# The user clicked the Auto-detect button on the 'Edit Attributes' form
for name, spec in ldda.datatype.metadata_spec.items():
@@ -1300,25 +1386,29 @@ class Admin( BaseController ):
ldda.datatype.set_meta( ldda )
ldda.datatype.after_edit( ldda )
trans.app.model.flush()
- msg = 'Attributes updated for dataset %s' % ldda.name
- return trans.response.send_redirect( web.url_for( controller='admin',
- action='library_dataset_dataset_association',
- id=id,
- library_id=library_id,
- information=True,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
+ msg = 'Attributes updated for library dataset %s' % ldda.name
+ return trans.fill_template( "/admin/library/ldda_info.mako",
+ ldda=ldda,
+ library_id=library_id,
+ datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
elif params.get( 'delete', False ):
# TODO: need to revamp the way we remove datasets from disk.
# The user selected the "Remove this dataset from the library" pop-up menu option
ldda.deleted = True
ldda.flush()
msg = 'Dataset %s has been removed from this library' % ldda.name
- trans.response.send_redirect( web.url_for( controller='admin',
- action='browse_library',
- id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
+ return trans.fill_template( "/admin/library/ldda_info.mako",
+ ldda=ldda,
+ library_id=library_id,
+ datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
ldda.datatype.before_edit( ldda )
if "dbkey" in ldda.datatype.metadata_spec and not ldda.metadata.dbkey:
# Copy dbkey into metadata, for backwards compatability
@@ -1327,14 +1417,12 @@ class Admin( BaseController ):
# case it resorts to the old dbkey. Setting the dbkey
# sets it properly in the metadata
ldda.metadata.dbkey = ldda.dbkey
- # let's not overwrite the imported datatypes module with the variable datatypes?
- ### the built-in 'id' is overwritten in lots of places as well
- ldatatypes = [x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys()]
- ldatatypes.sort()
return trans.fill_template( "/admin/library/ldda_info.mako",
ldda=ldda,
library_id=library_id,
datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
msg=msg,
messagetype=messagetype )
elif ids:
@@ -1359,33 +1447,55 @@ class Admin( BaseController ):
messagetype='error' ) )
if action == 'permissions':
if params.get( 'update_roles_button', False ):
- #p = util.Params( kwd )
permissions = {}
for k, v in trans.app.model.Dataset.permitted_actions.items():
in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ]
permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
for ldda in lddas:
+ # Set the DATASET permissions on the Dataset
trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, permissions )
ldda.dataset.refresh()
+ permissions = {}
+ for k, v in trans.app.model.Library.permitted_actions.items():
+ in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ]
+ permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
+ for ldda in lddas:
+ # Set the LIBRARY permissions on the LibraryDataset
+ # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions
+ trans.app.security_agent.set_all_library_permissions( ldda.library_dataset, permissions )
+ ldda.library_dataset.refresh()
+ # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation
+ trans.app.security_agent.set_all_library_permissions( ldda, permissions )
+ ldda.refresh()
msg = 'Permissions and roles have been updated on %d datasets' % len( lddas )
return trans.fill_template( "/admin/library/ldda_permissions.mako",
ldda=lddas,
library_id=library_id,
msg=msg,
messagetype=messagetype )
- # Ensure that the permissions across all datasets are identical. Otherwise, we can't update together.
- tmp = []
+ # Ensure that the permissions across all library items are identical, otherwise we can't update them together.
+ check_list = []
for ldda in lddas:
- perms = trans.app.security_agent.get_dataset_permissions( ldda.dataset )
- if perms not in tmp:
- tmp.append( perms )
- if len( tmp ) != 1:
- msg = 'The datasets you selected do not have identical permissions, so they can not be updated together'
- trans.response.send_redirect( web.url_for( controller='admin',
- action='browse_library',
- id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
+ permissions = []
+ # Check the library level permissions - the permissions on the LibraryDatasetDatasetAssociation
+ # will always be the same as the permissions on the associated LibraryDataset, so we only need to
+ # check one Library object
+ for library_permission in trans.app.security_agent.get_library_dataset_permissions( ldda.library_dataset ):
+ if library_permission.action not in permissions:
+ permissions.append( library_permission.action )
+ for dataset_permission in trans.app.security_agent.get_dataset_permissions( ldda.dataset ):
+ if dataset_permission.action not in permissions:
+ permissions.append( dataset_permission.action )
+ permissions.sort()
+ if not check_list:
+ check_list = permissions
+ if permissions != check_list:
+ msg = 'The datasets you selected do not have identical permissions, so they can not be updated together'
+ trans.response.send_redirect( web.url_for( controller='admin',
+ action='browse_library',
+ id=library_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='error' ) )
return trans.fill_template( "/admin/library/ldda_permissions.mako",
ldda=lddas,
library_id=library_id,
@@ -1393,7 +1503,7 @@ class Admin( BaseController ):
messagetype=messagetype )
@web.expose
@web.require_admin
- def add_history_datasets_to_library( self, trans, ids="", library_id=None, folder_id=None, **kwd ):
+ def add_history_datasets_to_library( self, trans, library_id, folder_id, hda_ids='', **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
@@ -1406,6 +1516,10 @@ class Admin( BaseController ):
id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
+ try:
+ replace_dataset = trans.app.model.LibraryDataset.get( int( params.get( 'replace_id', None ) ) )
+ except:
+ replace_dataset = None
# See if the current history is empty
history = trans.get_history()
history.refresh()
@@ -1416,238 +1530,339 @@ class Admin( BaseController ):
id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- if params.get( 'add_dataset_from_history_button', False ):
- dataset_names = []
- ids = util.listify( ids )
- if ids:
- for data_id in ids:
- data = trans.app.model.HistoryDatasetAssociation.get( data_id )
- if data:
- data.to_library_dataset_dataset_association( target_folder=folder )
- dataset_names.append( data.name )
+ if params.get( 'add_history_datasets_to_library_button', False ):
+ hda_ids = util.listify( hda_ids )
+ if hda_ids:
+ dataset_names = []
+ created_ldda_ids = ''
+ for hda_id in hda_ids:
+ hda = trans.app.model.HistoryDatasetAssociation.get( hda_id )
+ if hda:
+ ldda = hda.to_library_dataset_dataset_association( target_folder=folder )
+ created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( ldda.id ) )
+ dataset_names.append( ldda.name )
+ if replace_dataset:
+ # If we are replacing versions and we receive a list, we add all the datasets
+ # and set the last one in the list as current
+ replace_dataset.set_library_dataset_dataset_association( ldda )
+ # Copy the LDDA and LibrarDataset level permissions from replace_dataset to the new LDDA and LibraryDataset
+ trans.app.security_agent.copy_library_permissions( replace_dataset.library_dataset_dataset_association, ldda )
+ trans.app.security_agent.copy_library_permissions( replace_dataset.library_dataset, ldda.library_dataset )
+ else:
+ # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new
+ # LDDA and LibraryDataset.
+ trans.app.security_agent.copy_library_permissions( folder, ldda )
+ trans.app.security_agent.copy_library_permissions( folder, ldda.library_dataset )
else:
- msg = "The requested dataset id %s is invalid" % str( data_id )
+ msg = "The requested HistoryDatasetAssociation id %s is invalid" % str( hda_id )
return trans.response.send_redirect( web.url_for( controller='admin',
action='browse_library',
id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- if dataset_names:
- msg = "Added the following datasets to the library folder: %s" % ( ", ".join( dataset_names ) )
+ if created_ldda_ids:
+ created_ldda_ids = created_ldda_ids.lstrip( ',' )
+ ldda_id_list = created_ldda_ids.split( ',' )
+ total_added = len( ldda_id_list )
+ if replace_dataset:
+ msg = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset.name, folder.name )
+ else:
+ msg = "Added %d datasets to the library folder '%s' ( each is selected ). " % ( total_added, folder.name )
+ msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
return trans.response.send_redirect( web.url_for( controller='admin',
action='browse_library',
id=library_id,
+ created_ldda_ids=created_ldda_ids,
msg=util.sanitize_text( msg ),
- messagetype='done' ) )
+ messagetype='done' ) )
else:
- msg = 'Select at least one dataset from the list'
+ msg = 'Select at least one dataset from the list of active datasets in your current history'
messagetype = 'error'
- return trans.fill_template( "/admin/library/new_dataset.mako",
- history=history,
- library_id=library_id,
- folder=folder,
- msg=msg,
- messagetype=messagetype )
+ last_used_build = folder.genome_build
+ upload_option = params.get( 'upload_option', 'upload_file' )
+ # Send list of data formats to the form so the "extension" select list can be populated dynamically
+ file_formats = trans.app.datatypes_registry.upload_file_formats
+ # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically
+ def get_dbkey_options( last_used_build ):
+ for dbkey, build_name in util.dbnames:
+ yield build_name, dbkey, ( dbkey==last_used_build )
+ dbkeys = get_dbkey_options( last_used_build )
+ # Send list of roles to the form so the dataset can be associated with 1 or more of them.
+ roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all()
+ return trans.fill_template( "/admin/library/new_dataset.mako",
+ upload_option=upload_option,
+ library_id=library_id,
+ folder_id=folder_id,
+ file_formats=file_formats,
+ dbkeys=dbkeys,
+ last_used_build=last_used_build,
+ roles=roles,
+ history=history,
+ msg=msg,
+ messagetype=messagetype )
@web.expose
@web.require_admin
- def library_item_info_template( self, trans, id=None, new_element_count=0, library_id=None,
- folder_id=None, library_dataset_id=None, ldda_id=None, **kwd ):
+ def info_template( self, trans, library_id, id=None, num_fields=0, folder_id=None, ldda_id=None, library_dataset_id=None, **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
- new_element_count = int( new_element_count )
- liit = None
- if id:
+ if not num_fields:
+ num_fields = 0
+ else:
+ num_fields = int( num_fields )
+ if params.get( 'new_template', False ):
+ action = 'new_template'
+ else:
+ action = 'edit_template'
+ if folder_id:
+ library_item = trans.app.model.LibraryFolder.get( int( folder_id ) )
+ library_item_desc = 'folder'
+ response_action = 'folder'
+ response_id = folder_id
+ elif ldda_id:
+ library_item = trans.app.model.LibraryDatasetDatasetAssociation.get( int( ldda_id ) )
+ library_item_desc = 'library dataset <-> dataset association'
+ response_action = 'library_dataset_dataset_association'
+ response_id = ldda_id
+ elif library_dataset_id:
+ library_item = trans.app.model.LibraryDataset.get( int( library_dataset_id ) )
+ library_item_desc = 'library dataset'
+ response_action = 'library_dataset_dataset_association'
+ response_id = library_item.library_dataset_dataset_association.id
+ else:
+ library_item = trans.app.model.Library.get( int( library_id ) )
+ library_item_desc = 'library'
+ response_action = 'browse_library'
+ response_id = library_id
+ if action == 'new_template':
+ if params.get( 'create_info_template_button', False ):
+ return trans.fill_template( '/admin/library/new_info_template.mako',
+ library_item_name=library_item.name,
+ library_item_desc=library_item_desc,
+ num_fields=num_fields,
+ library_id=library_id,
+ folder_id=folder_id,
+ ldda_id=ldda_id,
+ library_dataset_id=library_dataset_id,
+ msg=msg,
+ messagetype=messagetype )
+ elif params.get( 'new_info_template_button', False ):
+ # Make sure at least 1 template field is filled in
+ # TODO: Eventually we'll enhance templates to allow for required and optional fields.
+ proceed = False
+ for i in range( int( params.get( 'set_num_fields', 0 ) ) ):
+ elem_name = params.get( 'new_element_name_%i' % i, None )
+ elem_description = params.get( 'new_element_description_%i' % i, None )
+ if elem_name or elem_description:
+ proceed = True
+ break
+ if not proceed:
+ msg = "At least 1 of the fields must be completed."
+ return trans.fill_template( '/admin/library/new_info_template.mako',
+ library_item_name=library_item.name,
+ library_item_desc=library_item_desc,
+ num_fields=num_fields,
+ library_id=library_id,
+ folder_id=folder_id,
+ ldda_id=ldda_id,
+ library_dataset_id=library_dataset_id,
+ msg=msg,
+ messagetype=messagetype )
+ # Create template then display edit screen
+ liit = trans.app.model.LibraryItemInfoTemplate()
+ liit.name = util.restore_text( params.get( 'name', '' ) )
+ liit.description = util.restore_text( params.get( 'description', '' ) )
+ liit.flush()
+ # Create template association
+ if folder_id:
+ liit_assoc = trans.app.model.LibraryFolderInfoTemplateAssociation()
+ liit_assoc.folder = trans.app.model.LibraryFolder.get( folder_id )
+ elif ldda_id:
+ liit_assoc = trans.app.model.LibraryDatasetDatasetInfoTemplateAssociation()
+ ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id )
+ liit_assoc.library_dataset_dataset_association = ldda
+ # This response_action method requires a folder_id
+ folder_id = ldda.library_dataset.folder.id
+ elif library_dataset_id:
+ liit_assoc = trans.app.model.LibraryDatasetInfoTemplateAssociation()
+ library_dataset = trans.app.model.LibraryDataset.get( library_dataset_id )
+ liit_assoc.library_dataset = library_dataset
+ # This response_action method requires a folder_id
+ folder_id = library_dataset.folder.id
+ else:
+ # We'll always be sent a library_id
+ liit_assoc = trans.app.model.LibraryInfoTemplateAssociation()
+ liit_assoc.library = trans.app.model.Library.get( library_id )
+ liit_assoc.library_item_info_template = liit
+ liit_assoc.flush()
+ # Create and add elements
+ for i in range( int( params.get( 'set_num_fields', 0 ) ) ):
+ elem_name = params.get( 'new_element_name_%i' % i, None )
+ elem_description = params.get( 'new_element_description_%i' % i, None )
+ if elem_description and not elem_name:
+ # If we have a description but no name, the description will be both
+ # ( a name cannot be empty, but a description can )
+ elem_name = elem_description
+ if elem_name:
+ # Skip any elements that have a missing name
+ liit.add_element( name=elem_name, description=elem_description )
+ msg = "The new information template has been created."
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action=response_action,
+ id=response_id,
+ library_id=library_id,
+ folder_id=folder_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='done' ) )
+ return trans.fill_template( '/admin/library/create_info_template.mako',
+ library_item=library_item,
+ library_id=library_id,
+ msg=msg,
+ messagetype=messagetype )
+ elif action == 'edit_template':
try:
- liit = trans.app.model.LibraryItemInfoTemplate.get( id )
+ liit = trans.app.model.LibraryItemInfoTemplate.get( int( id ) )
except:
msg = "Invalid library info template specified, id: %s" % str( id )
- # TODO: this should probably redirect to the dataset method...
return trans.response.send_redirect( web.url_for( controller='admin',
- action='browse_library',
- id=library_id,
+ action=response_action,
+ id=response_id,
+ library_id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- if params.get( 'liit_create_button', False ):
- # Create template then display edit screen
- liit = trans.app.model.LibraryItemInfoTemplate()
- liit.name = params.get( 'name', 'unnamed' )
- liit.description = params.get( 'description', '' )
- liit.flush()
- # Create template association
- if library_id:
- liit_assoc = trans.app.model.LibraryInfoTemplateAssociation()
- liit_assoc.library = trans.app.model.Library.get( library_id )
- elif folder_id:
- liit_assoc = trans.app.model.LibraryFolderInfoTemplateAssociation()
- liit_assoc.folder = trans.app.model.LibraryFolder.get( folder_id )
- elif library_dataset_id:
- liit_assoc = trans.app.model.LibraryDatasetInfoTemplateAssociation()
- liit_assoc.library_dataset = trans.app.model.LibraryDataset.get( library_dataset_id )
- elif ldda_id:
- liit_assoc = trans.app.model.LibraryDatasetDatasetInfoTemplateAssociation()
- liit_assoc.library_dataset_dataset_association = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id )
- liit_assoc.library_item_info_template = liit
- liit_assoc.flush()
- # Create and add elements
- for i in range( int( params.get( 'set_element_count', 0 ) ) ):
- elem_name = params.get( 'new_element_name_%i' % i, None )
- elem_description = params.get( 'new_element_description_%i' % i, None )
- # Skip any elements that have a missing name and description
- if not elem_name:
- # If we have a description but no name, the description will be both
- # ( a name cannot be empty, but a description can )
- elem_name = elem_description
- else:
- liit.add_element( name=elem_name, description=elem_description )
- elif params.get( 'liit_edit_button', False ):
- # Save changes to existing attributes, only set name if nonempty/nonNone is passed, but always set description
- name = params.get( 'name', None )
- if name:
- liit.name = name
- liit.description = params.get( 'description', '' )
- liit.flush()
- # Save changes to exisiting elements
- for elem_id in params.get( 'element_ids', [] ):
- liit_element = trans.app.model.LibraryItemInfoTemplateElement.get( elem_id )
- name = params.get( 'element_name_%s' % elem_id, None )
+ if params.get( 'edit_info_template_button', False ):
+ # Save changes to existing attributes, only set name if nonempty/nonNone is passed, but always set description
+ name = params.get( 'name', None )
if name:
- liit_element.name = name
- liit_element.description = params.get( 'element_description_%s' % elem_id, None )
- liit_element.flush()
- # Add new elements
- for i in range( int( params.get( 'set_element_count', 0 ) ) ):
- elem_name = params.get( 'new_element_name_%i' % i, None )
- elem_description = params.get( 'new_element_description_%i' % i, None )
- # Skip any elements that have a missing name and description
- if not elem_name:
- # If we have a description but no name, the description will be both
- # ( a name cannot be empty, but a description can )
- elem_name = elem_description
- if elem_name:
- liit.add_element( name=elem_name, description=elem_description )
- liit.refresh()
- msg = 'Information template %s has been updated' % liit.name
- if library_id:
- library_item_name = trans.app.model.Library.get( library_id ).name
- library_item_desc = 'library'
- elif folder_id:
- library_item_name = trans.app.model.LibraryFolder.get( folder_id ).name
- library_item_desc = 'folder'
- elif library_dataset_id:
- ld = trans.app.model.LibraryDataset.get( library_dataset_id )
- library_item_name = ld.name
- library_item_desc = 'library dataset'
- elif ldda_id:
- library_item_name = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ).name
- library_item_desc = 'library dataset <-> dataset association'
- else:
- library_item_name = 'unknown'
- library_item_desc = ''
- return trans.fill_template( "/admin/library/item_info_template.mako",
- liit=liit,
- new_element_count=new_element_count,
- library_id=library_id,
- library_dataset_id=library_dataset_id,
- ldda_id=ldda_id,
- folder_id=folder_id,
- library_item_name=library_item_name,
- library_item_desc=library_item_desc,
- msg=msg,
- messagetype=messagetype )
+ liit.name = name
+ liit.description = params.get( 'description', '' )
+ liit.flush()
+ # Save changes to exisiting elements
+ for elem_id in params.get( 'element_ids', [] ):
+ liit_element = trans.app.model.LibraryItemInfoTemplateElement.get( elem_id )
+ name = params.get( 'element_name_%s' % elem_id, None )
+ if name:
+ liit_element.name = name
+ liit_element.description = params.get( 'element_description_%s' % elem_id, None )
+ liit_element.flush()
+ # Add new elements
+ for i in range( int( params.get( 'set_num_fields', 0 ) ) ):
+ elem_name = params.get( 'new_element_name_%i' % i, None )
+ elem_description = params.get( 'new_element_description_%i' % i, None )
+ # Skip any elements that have a missing name and description
+ if not elem_name:
+ # If we have a description but no name, the description will be both
+ # ( a name cannot be empty, but a description can )
+ elem_name = elem_description
+ if elem_name:
+ liit.add_element( name=elem_name, description=elem_description )
+ liit.refresh()
+ msg = 'Information template %s has been updated' % liit.name
+ return trans.fill_template( "/admin/library/edit_info_template.mako",
+ liit=liit,
+ num_fields=num_fields,
+ library_id=library_id,
+ library_dataset_id=library_dataset_id,
+ ldda_id=ldda_id,
+ folder_id=folder_id,
+ library_item_name=library_item.name,
+ library_item_desc=library_item_desc,
+ msg=msg,
+ messagetype=messagetype )
@web.expose
@web.require_admin
- def library_item_info( self, trans, do_action='display', id=None, library_item_id=None, library_item_type=None, library_id=None, **kwd ):
+ def library_item_info( self, trans, library_id, id=None, library_item_id=None, library_item_type=None, **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
+ folder_id = None
if id:
item_info = trans.app.model.LibraryItemInfo.get( id )
else:
item_info = None
if library_item_type == 'library':
library_item = trans.app.model.Library.get( library_item_id )
- error_action = 'browse'
elif library_item_type == 'library_dataset':
library_item = trans.app.model.LibraryDataset.get( library_item_id )
- error_action = 'library_dataset'
elif library_item_type == 'folder':
library_item = trans.app.model.LibraryFolder.get( library_item_id )
elif library_item_type == 'library_dataset_dataset_association':
- error_action = 'folder'
library_item = trans.app.model.LibraryDatasetDatasetAssociation.get( library_item_id )
- error_action = 'library_dataset_dataset_association'
+ # This response_action method requires a folder_id
+ folder_id = library_item.library_dataset.folder.id
else:
library_item_type == None
library_item = None
- if not item_info and not library_item_type:
- msg = "Unable to perform requested action (%s)." % do_action
+ if not library_item:
+ msg = "Invalid library item type ( %s ) specified, id ( %s )" % ( str( library_item_type ), str( library_item_id ) )
return trans.response.send_redirect( web.url_for( controller='admin',
- action=error_action,
- id=library_item_id,
+ action='browse_library',
+ id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- #TODO: not sure we need the display option or the display_info.mako template any longer...
- if do_action == 'display':
- return trans.fill_template( "/admin/library/display_info.mako",
- item_info=item_info,
- msg=msg,
- messagetype=messagetype )
- elif do_action == 'new_info':
+ if params.get( 'new_info', False ):
if library_item:
- if trans.app.security_agent.allow_action( trans.user,
- trans.app.security_agent.permitted_actions.LIBRARY_ADD,
- library_item=library_item ):
- if params.get( 'create_new_info_button', False ):
- user = trans.get_user()
- #create new info then send back to make more
- library_item_info_template_id = params.get( 'library_item_info_template_id', None )
- library_item_info_template = trans.app.model.LibraryItemInfoTemplate.get( library_item_info_template_id )
- library_item_info = trans.app.model.LibraryItemInfo()
- library_item_info.library_item_info_template = library_item_info_template
- library_item_info.user = user
- library_item_info.flush()
- trans.app.security_agent.copy_library_permissions( library_item_info_template, library_item_info, user=user )
- for template_element in library_item_info_template.elements:
- info_element_value = params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id), None )
- info_element = trans.app.model.LibraryItemInfoElement()
- info_element.contents = info_element_value
- info_element.library_item_info_template_element = template_element
- info_element.library_item_info = library_item_info
- info_element.flush()
- info_association_class = None
- for item_class, permission_class, info_association_class in trans.app.security_agent.library_item_assocs:
- if isinstance( library_item, item_class ):
- break
- if info_association_class:
- library_item_info_association = info_association_class()
- library_item_info_association.set_library_item( library_item )
- library_item_info_association.library_item_info = library_item_info
- library_item_info_association.user = user
- library_item_info_association.flush()
- else:
- raise 'Invalid class (%s) specified for library_item (%s)' % ( library_item.__class__, library_item.__class__.__name__ )
- # TODO: make sure we don't need to set permissions on the association object.
- msg = 'The information has been saved. You can add more information if necessary.'
+ if params.get( 'create_new_info_button', False ):
+ library_item_info_template_id = params.get( 'library_item_info_template_id', None )
+ library_item_info_template = trans.app.model.LibraryItemInfoTemplate.get( int( library_item_info_template_id ) )
+ # Make sure at least 1 template field is filled in
+ # TODO: Eventually we'll enhance templates to allow for required and optional fields.
+ proceed = False
+ for template_element in library_item_info_template.elements:
+ if params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id ), None ):
+ proceed = True
+ break
+ if not proceed:
+ msg = "At least 1 of the fields must be completed."
return trans.response.send_redirect( web.url_for( controller='admin',
action=library_item_type,
id=library_item.id,
library_id=library_id,
+ folder_id=folder_id,
+ information=True,
msg=util.sanitize_text( msg ),
- messagetype='done' ) )
- return trans.fill_template( "/admin/library/new_info.mako",
- library_item=library_item,
- library_item_type=library_item_type,
- library_id=library_id,
- msg=msg,
- messagetype=messagetype )
- else:
- return trans.show_error_message( "You do not have permission to add information to this library item." )
+ messagetype='error' ) )
+ user = trans.get_user()
+ library_item_info = trans.app.model.LibraryItemInfo()
+ library_item_info.library_item_info_template = library_item_info_template
+ library_item_info.user = user
+ library_item_info.flush()
+ trans.app.security_agent.copy_library_permissions( library_item_info_template, library_item_info )
+ for template_element in library_item_info_template.elements:
+ info_element_value = params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id ), None )
+ info_element = trans.app.model.LibraryItemInfoElement()
+ info_element.contents = info_element_value
+ info_element.library_item_info_template_element = template_element
+ info_element.library_item_info = library_item_info
+ info_element.flush()
+ info_association_class = None
+ for item_class, permission_class, info_association_class in trans.app.security_agent.library_item_assocs:
+ if isinstance( library_item, item_class ):
+ break
+ if info_association_class:
+ library_item_info_association = info_association_class()
+ library_item_info_association.set_library_item( library_item, trans.user )
+ library_item_info_association.library_item_info = library_item_info
+ library_item_info_association.user = user
+ library_item_info_association.flush()
+ else:
+ raise 'Invalid class (%s) specified for library_item (%s)' % ( library_item.__class__, library_item.__class__.__name__ )
+ msg = 'The information has been saved'
+ return trans.response.send_redirect( web.url_for( controller='admin',
+ action=library_item_type,
+ id=library_item.id,
+ library_id=library_id,
+ folder_id=folder_id,
+ information=True,
+ msg=util.sanitize_text( msg ),
+ messagetype='done' ) )
+ return trans.fill_template( "/admin/library/new_info.mako",
+ library_id=library_id,
+ library_item=library_item,
+ library_item_type=library_item_type,
+ msg=msg,
+ messagetype=messagetype )
# TODO: add more functionality -> user's should be able to edit/delete, etc, and create/delete and edit templates
- return trans.fill_template( "/admin/library/display_info.mako",
- item_info=item_info,
- msg="Unable to perform requested action (%s)." % do_action,
- messagetype=messagetype )
@web.expose
@web.require_admin
def download_dataset_from_folder(self, trans, id, library_id=None, **kwd):
@@ -1680,7 +1895,7 @@ class Admin( BaseController ):
messagetype='error' ) )
@web.expose
@web.require_admin
- def datasets( self, trans, library_id=None, **kwd ):
+ def datasets( self, trans, library_id, **kwd ):
# This method is used by the select list labeled "Perform action on selected datasets"
# on the admin library browser.
params = util.Params( kwd )
@@ -1696,10 +1911,13 @@ class Admin( BaseController ):
messagetype='error' ) )
ldda_ids = util.listify( params.ldda_ids )
if params.action == 'edit':
+ # We need the folder containing the LibraryDatasetDatasetAssociation(s)
+ ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_ids[0] )
trans.response.send_redirect( web.url_for( controller='admin',
action='library_dataset_dataset_association',
- id=",".join( ldda_ids ),
library_id=library_id,
+ folder_id=ldda.library_dataset.folder.id,
+ id=",".join( ldda_ids ),
permissions=True,
msg=util.sanitize_text( msg ),
messagetype=messagetype ) )
diff --git a/lib/galaxy/web/controllers/dataset.py b/lib/galaxy/web/controllers/dataset.py
index acb0866d76a..de800dea7c1 100644
--- a/lib/galaxy/web/controllers/dataset.py
+++ b/lib/galaxy/web/controllers/dataset.py
@@ -1,15 +1,9 @@
+import logging, os, mimetypes, smtplib
+
from galaxy.web.base.controller import *
-
-import logging, os, sets, string, shutil, tempfile, StringIO, urllib
-import re, socket
-import mimetypes
-
-from galaxy import util, datatypes, jobs, web, util, model
-from galaxy.datatypes import sniff
-from galaxy.security import RBACAgent
+from galaxy import web, model
from cgi import escape, FieldStorage
-import smtplib
from email.MIMEText import MIMEText
import pkg_resources;
@@ -226,210 +220,3 @@ class DatasetInterface( BaseController ):
new_history_name = new_history_name,
done_msg = done_msg,
error_msg = error_msg )
-
-def add_file( trans, file_obj, name, extension, dbkey, roles, info='no info', space_to_tab=False,
- replace_dataset=None, permission_source=None, folder_id=None ):
- def check_gzip( temp_name ):
- # Utility method to check gzipped uploads
- temp = open( temp_name, "U" )
- magic_check = temp.read( 2 )
- temp.close()
- if magic_check != util.gzip_magic:
- return ( False, False )
- CHUNK_SIZE = 2**15 # 32Kb
- gzipped_file = gzip.GzipFile( temp_name )
- chunk = gzipped_file.read( CHUNK_SIZE )
- gzipped_file.close()
- return ( True, True )
- data_type = None
- temp_name = sniff.stream_to_file( file_obj )
- # See if we have a gzipped file, which, if it passes our restrictions, we'll uncompress on the fly.
- is_gzipped, is_valid = check_gzip( temp_name )
- if is_gzipped and not is_valid:
- raise BadFileException( "you attempted to upload an inappropriate file." )
- elif is_gzipped and is_valid:
- # We need to uncompress the temp_name file
- CHUNK_SIZE = 2**20 # 1Mb
- fd, uncompressed = tempfile.mkstemp()
- gzipped_file = gzip.GzipFile( temp_name )
- while 1:
- try:
- chunk = gzipped_file.read( CHUNK_SIZE )
- except IOError:
- os.close( fd )
- os.remove( uncompressed )
- raise BadFileException( 'problem uncompressing gzipped data.' )
- if not chunk:
- break
- os.write( fd, chunk )
- os.close( fd )
- gzipped_file.close()
- # Replace the gzipped file with the decompressed file
- shutil.move( uncompressed, temp_name )
- name = name.rstrip( '.gz' )
- data_type = 'gzip'
- if space_to_tab:
- line_count = sniff.convert_newlines_sep2tabs( temp_name )
- elif os.stat( temp_name ).st_size < 262144000: # 250MB
- line_count = sniff.convert_newlines( temp_name )
- else:
- if sniff.check_newlines( temp_name ):
- line_count = sniff.convert_newlines( temp_name )
- else:
- line_count = None
- if extension == 'auto':
- data_type = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order )
- else:
- data_type = extension
- if replace_dataset:
- library_dataset = replace_dataset
- else:
- library_dataset = trans.app.model.LibraryDataset( name=name, info=info, extension=data_type, dbkey=dbkey )
- library_dataset.flush()
- if permission_source:
- trans.app.security_agent.copy_library_permissions( permission_source, library_dataset, user=trans.get_user() )
- ldda = trans.app.model.LibraryDatasetDatasetAssociation( name=name,
- info=info,
- extension=data_type,
- dbkey=dbkey,
- library_dataset = library_dataset,
- create_dataset=True )
- ldda.flush()
- if permission_source:
- trans.app.security_agent.copy_library_permissions( permission_source, ldda, user=trans.get_user() )
- if not replace_dataset:
- folder = trans.app.model.LibraryFolder.get( folder_id )
- folder.add_dataset( library_dataset, genome_build=dbkey )
- library_dataset.library_dataset_dataset_association_id = ldda.id
- library_dataset.flush()
- if roles:
- for role in roles:
- dp = trans.app.model.DatasetPermissions( RBACAgent.permitted_actions.DATASET_ACCESS.action, ldda.dataset, role )
- dp.flush()
- shutil.move( temp_name, ldda.dataset.file_name )
- ldda.dataset.state = ldda.dataset.states.OK
- ldda.init_meta()
- if line_count is not None:
- try:
- ldda.set_peek( line_count=line_count )
- except:
- ldda.set_peek()
- else:
- ldda.set_peek()
- ldda.set_size()
- if ldda.missing_meta():
- ldda.datatype.set_meta( ldda )
- trans.app.model.flush()
- return ldda
-
-def upload_dataset( trans, controller=None, folder_id=None, replace_dataset=None, replace_id=None, permission_source=None, **kwd ):
- # This method is called from both the admin and library controllers
- params = util.Params( kwd )
- msg = util.restore_text( params.get( 'msg', '' ) )
- messagetype = params.get( 'messagetype', 'done' )
- dbkey = params.get( 'dbkey', '?' )
- extension = params.get( 'extension', 'auto' )
- data_file = params.get( 'file_data', '' )
- url_paste = params.get( 'url_paste', '' )
- server_dir = params.get( 'server_dir', 'None' )
- if data_file == '' and url_paste == '' and server_dir in [ 'None', '' ]:
- if trans.app.config.library_import_dir is not None:
- msg = 'Select a file, enter a URL or Text, or select a server directory.'
- else:
- msg = 'Select a file, enter a URL or enter Text.'
- trans.response.send_redirect( web.url_for( controller=controller,
- action='library_dataset_dataset_association',
- folder_id=folder_id,
- replace_id=replace_id,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
- space_to_tab = params.get( 'space_to_tab', False )
- if space_to_tab and space_to_tab not in [ "None", None ]:
- space_to_tab = True
- roles = []
- for role_id in util.listify( params.get( 'roles', [] ) ):
- roles.append( trans.app.model.Role.get( role_id ) )
- temp_name = ""
- data_list = []
- created_ldda_ids = ''
- if 'filename' in dir( data_file ):
- file_name = data_file.filename
- file_name = file_name.split( '\\' )[-1]
- file_name = file_name.split( '/' )[-1]
- created_ldda = add_file( trans,
- data_file.file,
- file_name,
- extension,
- dbkey,
- roles,
- info="uploaded file",
- space_to_tab=space_to_tab,
- replace_dataset=replace_dataset,
- permission_source=permission_source,
- folder_id=folder_id )
- created_ldda_ids = str( created_ldda.id )
- elif url_paste not in [ None, "" ]:
- if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0:
- url_paste = url_paste.replace( '\r', '' ).split( '\n' )
- for line in url_paste:
- line = line.rstrip( '\r\n' )
- if line:
- created_ldda = add_file( trans,
- urllib.urlopen( line ),
- line,
- extension,
- dbkey,
- roles,
- info="uploaded url",
- space_to_tab=space_to_tab,
- replace_dataset=replace_dataset,
- permission_source=permission_source,
- folder_id=folder_id )
- created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) )
- else:
- is_valid = False
- for line in url_paste:
- line = line.rstrip( '\r\n' )
- if line:
- is_valid = True
- break
- if is_valid:
- created_ldda = add_file( trans,
- StringIO.StringIO( url_paste ),
- 'Pasted Entry',
- extension,
- dbkey,
- roles,
- info="pasted entry",
- space_to_tab=space_to_tab,
- replace_dataset=replace_dataset,
- permission_source=permission_source,
- folder_id=folder_id )
- created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) )
- elif server_dir not in [ None, "", "None" ]:
- full_dir = os.path.join( trans.app.config.library_import_dir, server_dir )
- try:
- files = os.listdir( full_dir )
- except:
- log.debug( "Unable to get file list for %s" % full_dir )
- for file in files:
- full_file = os.path.join( full_dir, file )
- if not os.path.isfile( full_file ):
- continue
- created_ldda = add_file( trans,
- open( full_file, 'rb' ),
- file,
- extension,
- dbkey,
- roles,
- info="imported file",
- space_to_tab=space_to_tab,
- replace_dataset=replace_dataset,
- permission_source=permission_source,
- folder_id=folder_id )
- created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) )
- if created_ldda_ids:
- created_ldda_ids = created_ldda_ids.lstrip( ',' )
- return created_ldda_ids
- else:
- return ''
diff --git a/lib/galaxy/web/controllers/history.py b/lib/galaxy/web/controllers/history.py
index 5224fab844f..5868ec7efc6 100644
--- a/lib/galaxy/web/controllers/history.py
+++ b/lib/galaxy/web/controllers/history.py
@@ -1,4 +1,7 @@
from galaxy.web.base.controller import *
+from galaxy.web.framework.helpers.grids import *
+import webhelpers
+from datetime import datetime
from cgi import escape
log = logging.getLogger( __name__ )
@@ -6,83 +9,98 @@ log = logging.getLogger( __name__ )
# States for passing messages
SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error"
-class HistoryController( BaseController ):
+def time_ago( x ):
+ return webhelpers.date.distance_of_time_in_words( x, datetime.utcnow() )
+
+def iff( a, b, c ):
+ if a:
+ return b
+ else:
+ return c
+
+class HistoryListGrid( Grid ):
- @web.expose
- def index( self, trans ):
- return ""
+ title = "Stored histories"
+ model_class = model.History
+ default_sort_key = "-create_time"
+ columns = [
+ GridColumn( "Name", key="name",
+ link=( lambda item: iff( item.deleted, None, dict( operation="switch", id=item.id ) ) ),
+ attach_popup=True ),
+ GridColumn( "Datasets (by state)", method='_build_datasets_by_state', ncells=4 ),
+ GridColumn( "Status", method='_build_status' ),
+ GridColumn( "Age", key="create_time", format=time_ago ),
+ GridColumn( "Last update", key="update_time", format=time_ago ),
+ # Valid for filtering but invisible
+ GridColumn( "Deleted", key="deleted", visible=False )
+ ]
+ operations = [
+ GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ) ),
+ GridOperation( "Share", condition=( lambda item: not item.deleted ) ),
+ GridOperation( "Rename", condition=( lambda item: not item.deleted ) ),
+ GridOperation( "Delete", condition=( lambda item: not item.deleted ) ),
+ GridOperation( "Undelete", condition=( lambda item: item.deleted ) )
+ ]
+ standard_filters = [
+ GridColumnFilter( "Active", args=dict( deleted=False ) ),
+ GridColumnFilter( "Deleted", args=dict( deleted=True ) ),
+ GridColumnFilter( "All", args=dict( deleted='All' ) )
+ ]
+ default_filter = dict( deleted=False )
- @web.expose
- def list_as_xml( self, trans ):
- """
- XML history list for functional tests
- """
- return trans.fill_template( "/history/list_as_xml.mako" )
+ def get_current_item( self, trans ):
+ return trans.history
- @web.expose
- @web.require_login( "work with multiple histories" )
- def list( self, trans, id=[], operation=None, show_deleted = False, **kwd ):
- """
- List all available histories
- """
- # TODO: these two operations need to be updates still
- if operation:
- operation = operation.lower()
- if operation == "share":
- return self.share( trans, id, **kwd )
- elif operation == "rename":
- return self.rename( trans, id, **kwd )
- # Coerce ids to list
- if not isinstance( id, list ):
- id = id.split( "," )
- # Ensure ids are integers
- try:
- history_ids = map( int, id )
- except:
- return trans.show_error_message( "Invalid history id" )
- # Note that this page was loaded
- trans.log_event( "History id %s available" % str( id ) )
+ def apply_default_filter( self, trans, query ):
+ return query.filter_by( user=trans.user, purged=False )
+
+ def handle_operation( self, trans, operation, history_ids ):
# Display no message by default
status, message = None, None
refresh_history = False
- # If an operation was provided, load the histories and ensure they all
- # belong to the current user
- if history_ids and operation:
- histories = []
- for hid in history_ids:
- history = self.app.model.History.get( hid )
- if history:
- # Ensure history is owned by current user
- if history.user_id != None and trans.user:
- assert trans.user.id == history.user_id, "History does not belong to current user"
- histories.append( history )
- else:
- log.warn( "Invalid history id '%r' passed to list", hid )
- operation = operation.lower()
- if operation == "switch":
- status, message = self._list_switch( trans, histories )
- refresh_history = True
- elif operation == "share":
- ## Caught above for now
- pass
- elif operation == "rename":
- ## Caught above for now
- pass
- elif operation == "delete":
- status, message = self._list_delete( trans, histories )
- elif operation == "undelete":
- status, message = self._list_undelete( trans, histories )
- trans.app.model.flush()
+ # Load the histories and ensure they all belong to the current user
+ histories = []
+ for hid in history_ids:
+ history = model.History.get( hid )
+ if history:
+ # Ensure history is owned by current user
+ if history.user_id != None and trans.user:
+ assert trans.user.id == history.user_id, "History does not belong to current user"
+ histories.append( history )
+ else:
+ log.warn( "Invalid history id '%r' passed to list", hid )
+ operation = operation.lower()
+ if operation == "switch":
+ status, message = self._list_switch( trans, histories )
+ refresh_history = True
+ elif operation == "share":
+ ## Caught above for now
+ pass
+ elif operation == "rename":
+ ## Caught above for now
+ pass
+ elif operation == "delete":
+ status, message = self._list_delete( trans, histories )
+ elif operation == "undelete":
+ status, message = self._list_undelete( trans, histories )
+ trans.sa_session.flush()
# Render the list view
- return trans.fill_template( "/history/list.mako",
- ids = id,
- user = trans.user,
- current_history = trans.history,
- show_deleted = util.string_as_bool( show_deleted ),
- refresh_history = refresh_history,
- message_type = status,
- message = message
- )
+ return status, message
+
+ def _build_datasets_by_state( self, trans, history ):
+ rval = []
+ for state in ( 'ok', 'running', 'queued', 'error' ):
+ total = sum( 1 for d in history.active_datasets if d.state == state )
+ if total:
+ rval.append( '
%s
' % ( state, total ) )
+ else:
+ rval.append( '' )
+ return rval
+
+ def _build_status( self, trans, history ):
+ if history.deleted:
+ return "deleted"
+ return ""
def _list_delete( self, trans, histories ):
"""Delete histories"""
@@ -145,6 +163,51 @@ class HistoryController( BaseController ):
trans.log_event( "History switched to id: %s, name: '%s'" % (str(new_history.id), new_history.name ) )
# No message
return None, None
+
+class HistoryController( BaseController ):
+
+ @web.expose
+ def index( self, trans ):
+ return ""
+
+ @web.expose
+ def list_as_xml( self, trans ):
+ """
+ XML history list for functional tests
+ """
+ return trans.fill_template( "/history/list_as_xml.mako" )
+
+ _list_grid = HistoryListGrid()
+
+ @web.expose
+ @web.require_login( "work with multiple histories" )
+ def list( self, trans, *args, **kwargs ):
+ """
+ List all available histories
+ """
+ # TODO: these two operations need to be updates still
+ operation = kwargs.get( 'operation', None )
+ if operation:
+ operation = operation.lower()
+ if operation == "share":
+ return self.share( trans, **kwargs )
+ elif operation == "rename":
+ return self.rename( trans, **kwargs )
+ return self._list_grid( trans, *args, **kwargs )
+
+ @web.expose
+ def rename_async( self, trans, id=None, new_name=None ):
+ history = model.History.get( id )
+ # Check that the history exists, and is either owned by the current
+ # user (if logged in) or the current history
+ assert history is not None
+ if history.user is None:
+ assert history == trans.history
+ else:
+ assert history.user == trans.user
+ # Rename
+ history.name = new_name
+ trans.sa_session.flush()
## These have been moved from 'root' but not cleaned up
diff --git a/lib/galaxy/web/controllers/library.py b/lib/galaxy/web/controllers/library.py
index a5a0cba4251..f401c9c46de 100644
--- a/lib/galaxy/web/controllers/library.py
+++ b/lib/galaxy/web/controllers/library.py
@@ -1,5 +1,4 @@
from galaxy.web.base.controller import *
-from galaxy.web.controllers.dataset import upload_dataset
from galaxy.model.orm import *
from galaxy.datatypes import sniff
from galaxy import util
@@ -14,16 +13,31 @@ log = logging.getLogger( __name__ )
class Library( BaseController ):
@web.expose
- def index( self, trans ):
- return trans.fill_template( "/library/index.mako" )
+ def index( self, trans, **kwd ):
+ params = util.Params( kwd )
+ msg = util.restore_text( params.get( 'msg', '' ) )
+ messagetype = params.get( 'messagetype', 'done' )
+ return trans.fill_template( "/library/index.mako",
+ default_action=params.get( 'default_action', None ),
+ msg=msg,
+ messagetype=messagetype )
@web.expose
def browse_libraries( self, trans, **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
+ all_libraries = trans.app.model.Library.filter( trans.app.model.Library.table.c.deleted==False ).order_by( trans.app.model.Library.name ).all()
+ authorized_libraries = []
+ for library in all_libraries:
+ if trans.app.security_agent.allow_action( trans.user, trans.app.security_agent.permitted_actions.LIBRARY_ADD, library_item=library ) or \
+ trans.app.security_agent.allow_action( trans.user, trans.app.security_agent.permitted_actions.LIBRARY_MODIFY, library_item=library ) or \
+ trans.app.security_agent.allow_action( trans.user, trans.app.security_agent.permitted_actions.LIBRARY_MANAGE, library_item=library ) or \
+ trans.app.security_agent.check_folder_contents( trans.user, library ) or \
+ trans.app.security_agent.show_library_item( trans.user, library ):
+ authorized_libraries.append( library )
return trans.fill_template( '/library/browse_libraries.mako',
- libraries=trans.app.model.Library.filter( trans.app.model.Library.table.c.deleted==False ) \
- .order_by( trans.app.model.Library.name ).all(),
+ libraries=authorized_libraries,
+ default_action=params.get( 'default_action', None ),
msg=msg,
messagetype=messagetype )
@web.expose
@@ -36,6 +50,7 @@ class Library( BaseController ):
msg = "You must specify a library id."
return trans.response.send_redirect( web.url_for( controller='library',
action='browse_libraries',
+ default_action=params.get( 'default_action', None ),
msg=util.sanitize_text( msg ),
messagetype='error' ) )
library = library=trans.app.model.Library.get( id )
@@ -43,6 +58,7 @@ class Library( BaseController ):
msg = "Invalid library id ( %s )."
return trans.response.send_redirect( web.url_for( controller='library',
action='browse_libraries',
+ default_action=params.get( 'default_action', None ),
msg=util.sanitize_text( msg ),
messagetype='error' ) )
created_ldda_ids = params.get( 'created_ldda_ids', '' )
@@ -53,7 +69,79 @@ class Library( BaseController ):
msg=msg,
messagetype=messagetype )
@web.expose
- def datasets( self, trans, ldda_ids=[], library_id=None, **kwd ):
+ def library( self, trans, id=None, **kwd ):
+ params = util.Params( kwd )
+ msg = util.restore_text( params.get( 'msg', '' ) )
+ messagetype = params.get( 'messagetype', 'done' )
+ if not id:
+ msg = "You must specify a library."
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_libraries',
+ msg=util.sanitize_text( msg ),
+ messagetype='error' ) )
+ library = trans.app.model.Library.get( int( id ) )
+ if not library:
+ msg = "Invalid library id ( %s ) specified." % str( id )
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_libraries',
+ msg=util.sanitize_text( msg ),
+ messagetype='error' ) )
+ if params.get( 'delete', False ):
+ action = 'delete'
+ elif params.get( 'permissions', False ):
+ action = 'permissions'
+ else:
+ # 'information' is the default
+ action = 'information'
+ if action == 'information':
+ if params.get( 'rename_library_button', False ):
+ old_name = library.name
+ new_name = util.restore_text( params.name )
+ new_description = util.restore_text( params.description )
+ if not new_name:
+ msg = 'Enter a valid name'
+ return trans.fill_template( '/library/library_info.mako',
+ library=library,
+ restrict=params.get( 'restrict', False ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype='error' )
+ else:
+ library.name = new_name
+ library.description = new_description
+ library.flush()
+ msg = "Library '%s' has been renamed to '%s'" % ( old_name, new_name )
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='library',
+ id=id,
+ information=True,
+ msg=util.sanitize_text( msg ),
+ messagetype='done' ) )
+ return trans.fill_template( '/library/library_info.mako',
+ library=library,
+ restrict=params.get( 'restrict', False ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
+ elif action == 'permissions':
+ if params.get( 'update_roles_button', False ):
+ # The user clicked the Save button on the 'Associate With Roles' form
+ permissions = {}
+ for k, v in trans.app.model.Library.permitted_actions.items():
+ in_roles = [ trans.app.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_all_library_permissions( library, permissions )
+ library.refresh()
+ msg = "Permissions updated for library '%s'" % library.name
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='library',
+ id=id,
+ permissions=True,
+ msg=util.sanitize_text( msg ),
+ messagetype='done' ) )
+ return trans.fill_template( '/library/manage_library_permissions.mako', library=library, msg=msg, messagetype=messagetype )
+ @web.expose
+ def datasets( self, trans, library_id, ldda_ids='', **kwd ):
# This method is used by the select list labeled "Perform action on selected datasets"
# on the analysis library browser.
if not ldda_ids:
@@ -76,19 +164,29 @@ class Library( BaseController ):
messagetype='error' ) )
if params.do_action == 'add':
history = trans.get_history()
- for id in ldda_ids:
- dataset = trans.app.model.LibraryDatasetDatasetAssociation.get( id ).to_history_dataset_association()
- history.add_dataset( dataset )
- dataset.flush()
+ for ldda_id in ldda_ids:
+ ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id )
+ hda = ldda.to_history_dataset_association( target_history=history )
+ history.add_dataset( hda )
+ hda.flush()
history.flush()
- return trans.show_ok_message( "%i dataset(s) have been imported into your history" % len( ldda_ids ), refresh_frames=['history'] )
+ msg = "%i dataset(s) have been imported into your history" % len( ldda_ids )
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_library',
+ id=library_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='done' ) )
elif params.do_action == 'manage_permissions':
- # Do we need a security check here?
+ # We need the folder containing the LibraryDatasetDatasetAssociation(s)
+ ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_ids[0] )
trans.response.send_redirect( web.url_for( controller='library',
action='library_dataset_dataset_association',
- id=','.join( ldda_ids ),
library_id=library_id,
- **kwd ) )
+ folder_id=ldda.library_dataset.folder.id,
+ id=','.join( ldda_ids ),
+ permissions=True,
+ msg=util.sanitize_text( msg ),
+ messagetype=messagetype ) )
else:
# Can't use mkstemp - the file must not exist first
try:
@@ -214,72 +312,296 @@ class Library( BaseController ):
msg=msg,
messagetype='error' ) )
@web.expose
- def library_dataset_dataset_association( self, trans, id=None, name=None, info=None, folder_id=None,
- replace_id=None, refer_id=None, library_id=None, **kwd ):
+ def library_dataset( self, trans, id, library_id, **kwd ):
+ params = util.Params( kwd )
+ msg = util.restore_text( params.get( 'msg', '' ) )
+ messagetype = params.get( 'messagetype', 'done' )
+ if params.get( 'permissions', False ):
+ action = 'permissions'
+ elif params.get( 'versions', False ):
+ action = 'versions'
+ else:
+ # 'information' will be the default
+ action = 'information'
+ library_dataset = trans.app.model.LibraryDataset.get( id )
+ if not library_dataset:
+ msg = "Invalid library dataset specified, id: %s" %str( id )
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_library',
+ id=library_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='error' ) )
+ if action == 'information':
+ if params.get( 'edit_attributes_button', False ):
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
+ library_item=library_dataset ):
+ if params.get( 'edit_attributes_button', False ):
+ old_name = library_dataset.name
+ new_name = util.restore_text( params.get( 'name', '' ) )
+ new_info = util.restore_text( params.get( 'info', '' ) )
+ if not new_name:
+ msg = 'Enter a valid name'
+ messagetype = 'error'
+ else:
+ library_dataset.name = new_name
+ library_dataset.info = new_info
+ library_dataset.flush()
+ msg = "Dataset '%s' has been renamed to '%s'" % ( old_name, new_name )
+ messagetype = 'done'
+ else:
+ msg = "You are not authorized to change the attributes of this dataset"
+ messagetype = "error"
+ return trans.fill_template( '/library/library_dataset_info.mako',
+ library_dataset=library_dataset,
+ library_id=library_id,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
+ elif action == 'versions':
+ if params.get( 'change_version_button', False ):
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
+ library_item=library_dataset ):
+ target_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( kwd.get( 'set_lda_id' ) )
+ library_dataset.library_dataset_dataset_association = target_lda
+ trans.app.model.flush()
+ msg = 'The current version of this library dataset has been updated to be %s' % target_lda.name
+ messagetype = 'done'
+ else:
+ msg = "You are not authorized to change the versions of this dataset"
+ messagetype = "error"
+ return trans.fill_template( '/library/library_dataset_versions.mako',
+ library_dataset=library_dataset,
+ library_id=library_id,
+ msg=msg,
+ messagetype=messagetype )
+ elif action == 'permissions':
+ if params.get( 'update_roles_button', False ):
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MANAGE,
+ library_item=library_dataset ):
+ # The user clicked the Save button on the 'Associate With Roles' form
+ permissions = {}
+ for k, v in trans.app.model.Library.permitted_actions.items():
+ in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ]
+ permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
+ # Set the LIBRARY permissions on the LibraryDataset
+ # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions
+ trans.app.security_agent.set_all_library_permissions( library_dataset, permissions )
+ library_dataset.refresh()
+ # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation
+ trans.app.security_agent.set_all_library_permissions( library_dataset.library_dataset_dataset_association, permissions )
+ library_dataset.library_dataset_dataset_association.refresh()
+ msg = 'Permissions and roles have been updated for library dataset %s' % library_dataset.name
+ messagetype = 'done'
+ else:
+ msg = "You are not authorized to managed the permissions of this dataset"
+ messagetype = "error"
+ return trans.fill_template( '/library/library_dataset_permissions.mako',
+ library_dataset=library_dataset,
+ library_id=library_id,
+ msg=msg,
+ messagetype=messagetype )
+ @web.expose
+ def library_dataset_dataset_association( self, trans, library_id, folder_id, id=None, **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
dbkey = params.get( 'dbkey', '?' )
last_used_build = params.get( 'last_used_build', None )
- folder = replace_dataset = None
- if folder_id:
- folder = trans.app.model.LibraryFolder.get( folder_id )
- if not last_used_build:
- last_used_build = folder.genome_build
- permission_source = folder
+ folder = trans.app.model.LibraryFolder.get( folder_id )
+ if not folder:
+ msg = "Invalid library folder id ( %s ) specified" % str( folder_id )
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_library',
+ id=library_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='error' ) )
+ if not last_used_build:
+ last_used_build = folder.genome_build
try:
replace_dataset = trans.app.model.LibraryDataset.get( params.get( 'replace_id', None ) )
if not last_used_build:
last_used_build = replace_dataset.library_dataset_dataset_association.dbkey
- permission_source = replace_dataset
except:
replace_dataset = None
+ # Let's not overwrite the imported datatypes module with the variable datatypes?
+ # The built-in 'id' is overwritten in lots of places as well
+ ldatatypes = [ x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys() ]
+ ldatatypes.sort()
if id:
if params.get( 'permissions', False ):
action = 'permissions'
else:
# 'information' will be the default
action = 'information'
- # The user is updating the permissions on 1 or more datasets
if id.count( ',' ):
ids = id.split( ',' )
id = None
else:
ids = None
- if id:
- # id specified, display attributes form
- ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( id )
- if not ldda:
- msg = "Invalid LibraryDatasetDatasetAssociation specified, id: %s" % str( id )
- return trans.response.send_redirect( web.url_for( controller='library',
- action='browse_library',
- id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
- if action == 'permissions':
- if params.get( 'update_roles_button', False ):
- # The user clicked the Save button on the 'Associate With Roles' form
+ else:
+ ids = None
+ if id:
+ # ldda_id specified, display attributes form
+ ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( id )
+ if not ldda:
+ msg = "Invalid LibraryDatasetDatasetAssociation specified, id: %s" % str( id )
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_library',
+ id=library_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='error' ) )
+ if action == 'permissions':
+ if params.get( 'update_roles_button', False ):
+ # The user clicked the Save button on the 'Associate With Roles' form
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MANAGE,
+ library_item=ldda ) and \
+ trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS,
+ dataset=ldda.dataset ):
permissions = {}
for k, v in trans.app.model.Dataset.permitted_actions.items():
in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ]
permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
+ # Set the DATASET permissions on the Dataset
trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, permissions )
- # Set/display library security info
+ ldda.dataset.refresh()
permissions = {}
for k, v in trans.app.model.Library.permitted_actions.items():
- in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ]
+ in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ]
permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
+ # Set the LIBRARY permissions on the LibraryDataset
+ # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions
+ trans.app.security_agent.set_all_library_permissions( ldda.library_dataset, permissions )
+ ldda.library_dataset.refresh()
+ # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation
trans.app.security_agent.set_all_library_permissions( ldda, permissions )
- ldda.dataset.refresh()
+ ldda.refresh()
msg = "Permissions updated for dataset '%s'" % ldda.name
- return trans.response.send_redirect( web.url_for( controller='library',
- action='library_dataset_dataset_association',
- id=id,
- library_id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
- return trans.fill_template( '/library/ldda_permissions.mako', ldda=ldda, library_id=library_id, msg=msg, messagetype=messagetype )
- elif action == 'information':
+ messagetype = 'done'
+ else:
+ msg = "You are not authorized to change the permissions of dataset '%s'" % ldda.name
+ messagetype = 'error'
+ return trans.fill_template( '/library/ldda_permissions.mako',
+ ldda=ldda,
+ library_id=library_id,
+ msg=msg,
+ messagetype=messagetype )
+ elif action == 'information':
+ if params.get( 'change', False ):
+ # The user clicked the Save button on the 'Change data type' form
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
+ library_item=ldda ):
+ trans.app.datatypes_registry.change_datatype( ldda, params.datatype )
+ trans.app.model.flush()
+ msg = "Data type changed for library dataset '%s'" % ldda.name
+ messagetype = 'done'
+ else:
+ msg = "You are not authorized to change the data type of dataset '%s'" % ldda.name
+ messagetype = 'error'
+ return trans.fill_template( "/library/ldda_info.mako",
+ ldda=ldda,
+ library_id=library_id,
+ datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
+ elif params.get( 'save', False ):
+ # The user clicked the Save button on the 'Edit Attributes' form
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
+ library_item=ldda ):
+ old_name = ldda.name
+ new_name = util.restore_text( params.get( 'name', '' ) )
+ new_info = util.restore_text( params.get( 'info', '' ) )
+ if not new_name:
+ msg = 'Enter a valid name'
+ messagetype = 'error'
+ else:
+ ldda.name = new_name
+ ldda.info = new_info
+ # The following for loop will save all metadata_spec items
+ for name, spec in ldda.datatype.metadata_spec.items():
+ if spec.get("readonly"):
+ continue
+ optional = params.get( "is_" + name, None )
+ if optional and optional == 'true':
+ # optional element... == 'true' actually means it is NOT checked (and therefore ommitted)
+ setattr( ldda.metadata, name, None )
+ else:
+ setattr( ldda.metadata, name, spec.unwrap( params.get ( name, None ) ) )
+ ldda.metadata.dbkey = dbkey
+ ldda.datatype.after_edit( ldda )
+ trans.app.model.flush()
+ msg = 'Attributes updated for library dataset %s' % ldda.name
+ messagetype = 'done'
+ else:
+ msg = "you are not authorized to edit the attributes of dataset '%s'" % ldda.name
+ messagetype = 'error'
+ return trans.fill_template( "/library/ldda_info.mako",
+ ldda=ldda,
+ library_id=library_id,
+ datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
+ elif params.get( 'detect', False ):
+ # The user clicked the Auto-detect button on the 'Edit Attributes' form
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
+ library_item=ldda ):
+ for name, spec in ldda.datatype.metadata_spec.items():
+ # We need to be careful about the attributes we are resetting
+ if name not in [ 'name', 'info', 'dbkey' ]:
+ if spec.get( 'default' ):
+ setattr( ldda.metadata, name, spec.unwrap( spec.get( 'default' ) ) )
+ ldda.datatype.set_meta( ldda )
+ ldda.datatype.after_edit( ldda )
+ trans.app.model.flush()
+ msg = 'Attributes updated for library dataset %s' % ldda.name
+ messagetype = 'done'
+ else:
+ msg = "you are not authorized to edit the attributes of dataset '%s'" % ldda.name
+ messagetype = 'error'
+ return trans.fill_template( "/library/ldda_info.mako",
+ ldda=ldda,
+ library_id=library_id,
+ datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
+ elif params.get( 'delete', False ):
+ # TODO: need to revamp the way we remove datasets from disk.
+ # The user selected the "Remove this dataset from the library" pop-up menu option
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
+ library_item=folder ):
+ ldda.deleted = True
+ ldda.flush()
+ msg = 'Dataset %s has been removed from this library' % ldda.name
+ messagetype = 'done'
+ else:
+ msg = "you are not authorized to delete dataset '%s'" % ldda.name
+ messagetype = 'error'
+ return trans.fill_template( "/library/ldda_info.mako",
+ ldda=ldda,
+ library_id=library_id,
+ datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
+ library_item=ldda ):
ldda.datatype.before_edit( ldda )
if "dbkey" in ldda.datatype.metadata_spec and not ldda.metadata.dbkey:
# Copy dbkey into metadata, for backwards compatability
@@ -288,100 +610,141 @@ class Library( BaseController ):
# case it resorts to the old dbkey. Setting the dbkey
# sets it properly in the metadata
ldda.metadata.dbkey = ldda.dbkey
- # let's not overwrite the imported datatypes module with the variable datatypes?
- ### the built-in 'id' is overwritten in lots of places as well
- ldatatypes = [x for x in trans.app.datatypes_registry.datatypes_by_extension.iterkeys()]
- ldatatypes.sort()
- return trans.fill_template( "/library/ldda_info.mako",
- ldda=ldda,
- library_id=library_id,
- datatypes=ldatatypes,
- msg=msg,
- messagetype=messagetype )
- elif ids:
- # Multiple ids specfied, display permission form, permissions will be updated for all simultaneously.
- lddas = []
- for id in [ int( id ) for id in ids ]:
- ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( id )
- if ldda is None:
- msg = 'You specified an invalid LibraryDatasetDatasetAssociation id: %s' %str( id )
- trans.response.send_redirect( web.url_for( controller='library',
- action='browse_library',
- id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
- lddas.append( ldda )
- if len( lddas ) < 2:
- msg = 'You must specify at least two datasets on which to modify permissions, ids you sent: %s' % str( ids )
+ return trans.fill_template( "/library/ldda_info.mako",
+ ldda=ldda,
+ library_id=library_id,
+ datatypes=ldatatypes,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
+ msg=msg,
+ messagetype=messagetype )
+ elif ids:
+ # Multiple ids specfied, display permission form, permissions will be updated for all simultaneously.
+ lddas = []
+ for id in [ int( id ) for id in ids ]:
+ ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( id )
+ if ldda is None:
+ msg = 'You specified an invalid LibraryDatasetDatasetAssociation id: %s' %str( id )
trans.response.send_redirect( web.url_for( controller='library',
action='browse_library',
id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- if action == 'permissions':
- if params.get( 'update_roles_button', False ):
+ lddas.append( ldda )
+ if len( lddas ) < 2:
+ msg = 'You must specify at least two datasets on which to modify permissions, ids you sent: %s' % str( ids )
+ trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_library',
+ id=library_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='error' ) )
+ if action == 'permissions':
+ if params.get( 'update_roles_button', False ):
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MANAGE,
+ library_item=ldda ) and \
+ trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS,
+ dataset=ldda.dataset ):
permissions = {}
for k, v in trans.app.model.Dataset.permitted_actions.items():
in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ]
permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
for ldda in lddas:
+ # Set the DATASET permissions on the Dataset
trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, permissions )
ldda.dataset.refresh()
+ permissions = {}
+ for k, v in trans.app.model.Library.permitted_actions.items():
+ in_roles = [ trans.app.model.Role.get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ]
+ permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
+ for ldda in lddas:
+ # Set the LIBRARY permissions on the LibraryDataset
+ # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions
+ trans.app.security_agent.set_all_library_permissions( ldda.library_dataset, permissions )
+ ldda.library_dataset.refresh()
+ # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation
+ trans.app.security_agent.set_all_library_permissions( ldda, permissions )
+ ldda.refresh()
msg = 'Permissions and roles have been updated on %d datasets' % len( lddas )
- return trans.fill_template( "/library/ldda_permissions.mako",
- ldda=lddas,
- library_id=library_id,
- msg=msg,
- messagetype=messagetype )
- # Ensure that the permissions across all datasets are identical. Otherwise, we can't update together.
- tmp = []
- for ldda in lddas:
- perms = trans.app.security_agent.get_dataset_permissions( ldda.dataset )
- if perms not in tmp:
- tmp.append( perms )
- if len( tmp ) != 1:
- msg = 'The datasets you selected do not have identical permissions, so they can not be updated together'
- trans.response.send_redirect( web.url_for( controller='library',
- action='browse_library',
- id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
- else:
+ messagetype = 'done'
+ else:
+ msg = "You are not authorized to change the permissions of dataset '%s'" % ldda.name
+ messagetype = 'error'
return trans.fill_template( "/library/ldda_permissions.mako",
ldda=lddas,
library_id=library_id,
msg=msg,
messagetype=messagetype )
- if not folder and not replace_dataset:
- msg = "Invalid library target specified (LibraryFolder id: %s, LibraryDataset id: %s)" % ( str( folder_id ), str( replace_id ) )
- return trans.response.send_redirect( web.url_for( controller='library',
- action='browse_library',
- id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
- if ( folder and trans.app.security_agent.allow_action( trans.user,
- trans.app.security_agent.permitted_actions.LIBRARY_ADD,
- library_item=folder ) ) or \
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MANAGE,
+ library_item=ldda ) and \
+ trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS,
+ dataset=ldda.dataset ):
+ # Ensure that the permissions across all library items are identical, otherwise we can't update them together.
+ check_list = []
+ for ldda in lddas:
+ permissions = []
+ # Check the library level permissions - the permissions on the LibraryDatasetDatasetAssociation
+ # will always be the same as the permissions on the associated LibraryDataset, so we only need to
+ # check one Library object
+ for library_permission in trans.app.security_agent.get_library_dataset_permissions( ldda.library_dataset ):
+ if library_permission.action not in permissions:
+ permissions.append( library_permission.action )
+ for dataset_permission in trans.app.security_agent.get_dataset_permissions( ldda.dataset ):
+ if dataset_permission.action not in permissions:
+ permissions.append( dataset_permission.action )
+ permissions.sort()
+ if not check_list:
+ check_list = permissions
+ if permissions != check_list:
+ msg = 'The datasets you selected do not have identical permissions, so they can not be updated together'
+ trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_library',
+ id=library_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='error' ) )
+ else:
+ msg = "You are not authorized to change the permissions of dataset '%s'" % ldda.name
+ messagetype = 'error'
+ return trans.fill_template( "/library/ldda_permissions.mako",
+ ldda=lddas,
+ library_id=library_id,
+ msg=msg,
+ messagetype=messagetype )
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_ADD,
+ library_item=folder ) or \
( replace_dataset and trans.app.security_agent.allow_action( trans.user,
- trans.app.security_agent.permitted_actions.LIBRARY_ADD,
+ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
library_item=replace_dataset ) ):
if params.get( 'new_dataset_button', False ):
- created_ldda_ids = upload_dataset( trans,
- controller='library',
- folder_id=folder_id,
- replace_dataset=replace_dataset,
- permission_source=permission_source,
- **kwd )
+ created_ldda_ids = trans.webapp.controllers[ 'library_dataset' ].upload_dataset( trans,
+ controller='library',
+ library_id=library_id,
+ folder_id=folder_id,
+ replace_dataset=replace_dataset,
+ **kwd )
if created_ldda_ids:
- total_added = len( created_ldda_ids.split( ',' ) )
+ ldda_id_list = created_ldda_ids.split( ',' )
+ total_added = len( ldda_id_list )
+ if replace_dataset:
+ msg = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset.name, folder.name )
+ else:
+ msg = "Added %d datasets to the library folder '%s' ( each is selected ). " % ( total_added, folder.name )
+ # Since permissions on all LibraryDatasetDatasetAssociations must be the same at this point, we only need
+ # to check one of them to see if the current user can manage permissions on them.
+ check_ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id_list[0] )
if trans.app.security_agent.allow_action( trans.user,
trans.app.security_agent.permitted_actions.LIBRARY_MANAGE,
- library_item=permission_source ):
- msg = "%i new datasets added to the library ( each is selected below ). " % total_added + \
- "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
- default_action = 'manage_permissions'
+ library_item=check_ldda ):
+ if replace_dataset:
+ default_action = ''
+ else:
+ msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
+ default_action = 'manage_permissions'
else:
- msg = "%i new datasets added to the library." % total_added
default_action = 'add'
trans.response.send_redirect( web.url_for( controller='library',
action='browse_library',
@@ -399,148 +762,149 @@ class Library( BaseController ):
created_ldda_ids=created_ldda_ids,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- elif params.get( "add_dataset_from_history_button", False):
- # See if the current history is empty
- history = trans.get_history()
- history.refresh()
- if not history.active_datasets:
- msg = 'Your current history is empty'
- return trans.response.send_redirect( web.url_for( controller='library',
- action='browse_library',
- id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
- hids = util.listify( params.get( 'hids', [] ) )
- dataset_names = []
- if hids:
- for data_id in hids:
- data = trans.app.model.HistoryDatasetAssociation.get( data_id )
- if data:
- data = data.to_library_dataset_dataset_association( target_folder=folder )
- dataset_names.append( data.name )
- if folder:
- folder.add_dataset( data )
- elif replace_dataset:
- # If we are replacing versions and we receive a list,
- # we add all the datasets and set the last one in the list as current
- if trans.app.security_agent.allow_action( trans.user,
- trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
- library_item=replace_dataset ):
- replace_dataset.set_library_dataset_dataset_association( data )
- else:
- data.library_dataset = replace_dataset
- data.flush()
- else:
- msg = "The requested dataset id %s is invalid" % str( data_id )
- return trans.response.send_redirect( web.url_for( controller='library',
- action='browse_library',
- id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
- if dataset_names:
- if folder:
- msg = "Added %d datasets to the library folder '%s'" % ( len( dataset_names ), folder.name )
- else:
- msg = "Added %d datasets to the versioned library dataset '%s'" % ( len( dataset_names ), replace_dataset.name )
- return trans.response.send_redirect( web.url_for( controller='library',
- action='browse_library',
- id=library_id,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
- else:
- # No dataset(s) specified, display upload form
- file_formats = trans.app.datatypes_registry.upload_file_formats
- # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically
- def get_dbkey_options( last_used_build ):
- for dbkey, build_name in util.dbnames:
- yield build_name, dbkey, ( dbkey==last_used_build )
- dbkeys = get_dbkey_options( last_used_build )
- # Send list of roles to the form so the dataset can be associated with 1 or more of them.
- roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all()
- msg = 'Select at least one dataset from the list'
- return trans.fill_template( "/library/new_dataset.mako",
- history=history,
- library_id=library_id,
- folder_id=folder_id,
- file_formats=file_formats,
- dbkeys=dbkeys,
- last_used_build=last_used_build,
- roles=roles,
- replace_dataset=replace_dataset,
- msg=msg,
- messagetype='error' )
-
- #copied...
- def get_dbkey_options( last_used_build ):
- for dbkey, build_name in util.dbnames:
- yield build_name, dbkey, ( dbkey==last_used_build )
- return trans.fill_template( "/library/new_dataset.mako",
- library_id=library_id,
- folder_id=folder_id,
- replace_dataset=replace_dataset,
- file_formats=trans.app.datatypes_registry.upload_file_formats,
- dbkeys=get_dbkey_options( '?' ),
- last_used_build=last_used_build,
- msg=msg,
- messagetype=messagetype )
+ if not id or replace_dataset:
+ upload_option = params.get( 'upload_option', 'upload_file' )
+ # No dataset(s) specified, so display the upload form. Send list of data formats to the form
+ # so the "extension" select list can be populated dynamically
+ file_formats = trans.app.datatypes_registry.upload_file_formats
+ # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically
+ def get_dbkey_options( last_used_build ):
+ for dbkey, build_name in util.dbnames:
+ yield build_name, dbkey, ( dbkey==last_used_build )
+ dbkeys = get_dbkey_options( last_used_build )
+ # Send list of roles to the form so the dataset can be associated with 1 or more of them.
+ roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all()
+ # Send the current history to the form to enable importing datasets from history to library
+ history = trans.get_history()
+ history.refresh()
+ return trans.fill_template( '/library/new_dataset.mako',
+ upload_option=upload_option,
+ library_id=library_id,
+ folder_id=folder_id,
+ file_formats=file_formats,
+ dbkeys=dbkeys,
+ last_used_build=last_used_build,
+ roles=roles,
+ history=history,
+ msg=msg,
+ messagetype=messagetype,
+ replace_dataset=replace_dataset )
@web.expose
- def library_dataset( self, trans, id, name=None, info=None, refer_id=None, library_id=None, **kwd ):
+ def add_history_datasets_to_library( self, trans, library_id, folder_id, hda_ids='', **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
- library_dataset = trans.app.model.LibraryDataset.get( id )
- if not library_dataset:
- msg = "Invalid library dataset specified, id: %s" %str( id )
+ try:
+ folder = trans.app.model.LibraryFolder.get( int( folder_id ) )
+ except:
+ msg = "Invalid folder id: %s" % str( folder_id )
return trans.response.send_redirect( web.url_for( controller='library',
action='browse_library',
id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- if refer_id:
- refered_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( refer_id )
- else:
- refered_lda=None
- if params.get( 'save', False ):
- if trans.app.security_agent.allow_action( trans.user,
- trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
- library_item=library_dataset ):
- library_dataset.name = name
- library_dataset.info = info
- target_lda = trans.app.model.LibraryDatasetDatasetAssociation.get( params.get( 'set_lda_id' ) )
- library_dataset.library_dataset_dataset_association = target_lda
- trans.app.model.flush()
- msg = 'Attributes updated for library dataset %s' % library_dataset.name
- messagetype = 'done'
- else:
- msg = "You are not authorized to changed the attributes of this dataset"
- messagetype = "error"
+ try:
+ replace_dataset = trans.app.model.LibraryDataset.get( params.get( 'replace_id', None ) )
+ except:
+ replace_dataset = None
+ # See if the current history is empty
+ history = trans.get_history()
+ history.refresh()
+ if not history.active_datasets:
+ msg = 'Your current history is empty'
return trans.response.send_redirect( web.url_for( controller='library',
- action='library_dataset',
- id=id,
- library_id=library_id,
+ action='browse_library',
+ id=library_id,
msg=util.sanitize_text( msg ),
- messagetype=messagetype ) )
- elif 'update_roles_button' in kwd:
- if trans.app.security_agent.allow_action( trans.user,
- trans.app.security_agent.permitted_actions.LIBRARY_MANAGE,
- library_item=library_dataset ):
- # The user clicked the Save button on the 'Associate With Roles' form
- permissions = {}
- for k, v in trans.app.model.Library.permitted_actions.items():
- in_roles = [ trans.app.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_all_library_permissions( library_dataset, permissions )
- library_dataset.refresh()
- msg = 'Permissions updated for library dataset %s' % library_dataset.name
- else:
- msg = "Permission Denied"
- messagetype = "error"
- return trans.fill_template( "/library/manage_library_dataset.mako",
- library_dataset=library_dataset,
- refered_lda=refered_lda,
- library_id=library_id,
- msg=msg,
- messagetype=messagetype )
+ messagetype='error' ) )
+ if params.get( 'add_history_datasets_to_library_button', False ):
+ hda_ids = util.listify( hda_ids )
+ if hda_ids:
+ dataset_names = []
+ created_ldda_ids = ''
+ for hda_id in hda_ids:
+ hda = trans.app.model.HistoryDatasetAssociation.get( hda_id )
+ if hda:
+ ldda = hda.to_library_dataset_dataset_association( target_folder=folder )
+ created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( ldda.id ) )
+ dataset_names.append( ldda.name )
+ if replace_dataset:
+ # If we are replacing versions and we receive a list, we add all the datasets
+ # and set the last one in the list as current
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MODIFY,
+ library_item=replace_dataset ):
+ replace_dataset.set_library_dataset_dataset_association( ldda )
+ else:
+ ldda.library_dataset = replace_dataset
+ # Copy the LDDA and LibraryDataset level permissions from replace_dataset to the new LDDA and LibraryDataset
+ trans.app.security_agent.copy_library_permissions( replace_dataset.library_dataset_dataset_association, ldda )
+ trans.app.security_agent.copy_library_permissions( replace_dataset.library_dataset, ldda.library_dataset )
+ else:
+ # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new
+ # LDDA and LibraryDataset.
+ trans.app.security_agent.copy_library_permissions( folder, ldda )
+ trans.app.security_agent.copy_library_permissions( folder, ldda.library_dataset )
+ else:
+ msg = "The requested HistoryDatasetAssociation id %s is invalid" % str( hda_id )
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_library',
+ id=library_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='error' ) )
+ if created_ldda_ids:
+ created_ldda_ids = created_ldda_ids.lstrip( ',' )
+ ldda_id_list = created_ldda_ids.split( ',' )
+ total_added = len( ldda_id_list )
+ if replace_dataset:
+ msg = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset.name, folder.name )
+ else:
+ msg = "Added %d datasets to the library folder '%s' ( each is selected ). " % ( total_added, folder.name )
+ # Since permissions on all LibraryDatasetDatasetAssociations must be the same at this point, we only need
+ # to check one of them to see if the current user can manage permissions on them.
+ check_ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id_list[0] )
+ if trans.app.security_agent.allow_action( trans.user,
+ trans.app.security_agent.permitted_actions.LIBRARY_MANAGE,
+ library_item=check_ldda ):
+ if replace_dataset:
+ default_action = ''
+ else:
+ msg += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
+ default_action = 'manage_permissions'
+ else:
+ default_action = 'add'
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action='browse_library',
+ id=library_id,
+ created_ldda_ids=created_ldda_ids.lstrip( ',' ),
+ default_action=default_action,
+ msg=util.sanitize_text( msg ),
+ messagetype='done' ) )
+ else:
+ msg = 'Select at least one dataset from the list of active datasets in your current history'
+ messagetype = 'error'
+ last_used_build = folder.genome_build
+ upload_option = params.get( 'upload_option', 'upload_file' )
+ # Send list of data formats to the form so the "extension" select list can be populated dynamically
+ file_formats = trans.app.datatypes_registry.upload_file_formats
+ # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically
+ def get_dbkey_options( last_used_build ):
+ for dbkey, build_name in util.dbnames:
+ yield build_name, dbkey, ( dbkey==last_used_build )
+ dbkeys = get_dbkey_options( last_used_build )
+ # Send list of roles to the form so the dataset can be associated with 1 or more of them.
+ roles = trans.app.model.Role.filter( trans.app.model.Role.table.c.deleted==False ).order_by( trans.app.model.Role.c.name ).all()
+ return trans.fill_template( "/library/new_dataset.mako",
+ upload_option=upload_option,
+ library_id=library_id,
+ folder_id=folder_id,
+ file_formats=file_formats,
+ dbkeys=dbkeys,
+ last_used_build=last_used_build,
+ roles=roles,
+ history=history,
+ msg=msg,
+ messagetype=messagetype )
@web.expose
def folder( self, trans, id, library_id, **kwd ):
params = util.Params( kwd )
@@ -557,7 +921,7 @@ class Library( BaseController ):
action = 'information'
folder = trans.app.model.LibraryFolder.get( int( id ) )
if not folder:
- msg = "Invalid folder specified, id: %s" %str( id )
+ msg = "Invalid folder specified, id: %s" % str( id )
return trans.response.send_redirect( web.url_for( controller='library',
action='browse_library',
id=library_id,
@@ -565,8 +929,6 @@ class Library( BaseController ):
messagetype='error' ) )
if action == 'new':
if params.new == 'submitted':
- # Create the new folder, then return manage_folder template for new folder.
- # New folders default to having the same permissions as their parent folder
new_folder = trans.app.model.LibraryFolder( name=util.restore_text( params.name ),
description=util.restore_text( params.description ) )
# We are associating the last used genome build with folders, so we will always
@@ -575,13 +937,12 @@ class Library( BaseController ):
new_folder.genome_build = util.dbnames.default_value
folder.add_folder( new_folder )
new_folder.flush()
+ # New folders default to having the same permissions as their parent folder
trans.app.security_agent.copy_library_permissions( folder, new_folder, user=trans.get_user() )
msg = "New folder named '%s' has been added to the library" % new_folder.name
return trans.response.send_redirect( web.url_for( controller='library',
- action='folder',
- id=id,
- library_id=library_id,
- information=True,
+ action='browse_library',
+ id=library_id,
msg=util.sanitize_text( msg ),
messagetype='done' ) )
return trans.fill_template( '/library/new_folder.mako',
@@ -602,6 +963,8 @@ class Library( BaseController ):
return trans.fill_template( "/library/folder_info.mako",
folder=folder,
library_id=library_id,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
msg=msg,
messagetype='error' )
else:
@@ -621,11 +984,15 @@ class Library( BaseController ):
return trans.fill_template( "/library/folder_info.mako",
folder=folder,
library_id=library_id,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
msg=msg,
messagetype='error' )
return trans.fill_template( '/library/folder_info.mako',
folder=folder,
library_id=library_id,
+ restrict=params.get( 'restrict', True ),
+ render_templates=params.get( 'render_templates', False ),
msg=msg,
messagetype=messagetype )
elif action == 'permissions':
@@ -663,266 +1030,265 @@ class Library( BaseController ):
msg=msg,
messagetype=messagetype )
@web.expose
- def library( self, trans, id=None, **kwd ):
+ def info_template( self, trans, library_id, id=None, num_fields=0, folder_id=None, ldda_id=None, library_dataset_id=None, **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
- if not id:
- msg = "You must specify a library."
- return trans.response.send_redirect( web.url_for( controller='library',
- action='browse_libraries',
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
- library = trans.app.model.Library.get( int( id ) )
- if not library:
- msg = "Invalid library id ( %s ) specified." % str( id )
- return trans.response.send_redirect( web.url_for( controller='library',
- action='browse_libraries',
- msg=util.sanitize_text( msg ),
- messagetype='error' ) )
- if params.get( 'delete', False ):
- action = 'delete'
- elif params.get( 'permissions', False ):
- action = 'permissions'
+ if not num_fields:
+ num_fields = 0
else:
- # 'information' is the default
- action = 'information'
- if action == 'information':
- if params.get( 'rename_library_button', False ):
- old_name = library.name
- new_name = util.restore_text( params.name )
- new_description = util.restore_text( params.description )
- if not new_name:
- msg = 'Enter a valid name'
- return trans.fill_template( '/library/library_info.mako', library=library, msg=msg, messagetype='error' )
+ num_fields = int( num_fields )
+ if params.get( 'new_template', False ):
+ action = 'new_template'
+ else:
+ action = 'edit_template'
+ if folder_id:
+ library_item = trans.app.model.LibraryFolder.get( int( folder_id ) )
+ library_item_desc = 'folder'
+ response_action = 'folder'
+ response_id = folder_id
+ elif ldda_id:
+ library_item = trans.app.model.LibraryDatasetDatasetAssociation.get( int( ldda_id ) )
+ library_item_desc = 'library dataset <-> dataset association'
+ response_action = 'library_dataset_dataset_association'
+ response_id = ldda_id
+ elif library_dataset_id:
+ library_item = trans.app.model.LibraryDataset.get( int( library_dataset_id ) )
+ library_item_desc = 'library dataset'
+ response_action = 'library_dataset_dataset_association'
+ response_id = library_item.library_dataset_dataset_association.id
+ else:
+ library_item = trans.app.model.Library.get( int( library_id ) )
+ library_item_desc = 'library'
+ response_action = 'browse_library'
+ response_id = library_id
+ if action == 'new_template':
+ if params.get( 'create_info_template_button', False ):
+ return trans.fill_template( '/library/new_info_template.mako',
+ library_item=library_item,
+ library_item_name=library_item.name,
+ library_item_desc=library_item_desc,
+ num_fields=num_fields,
+ library_id=library_id,
+ folder_id=folder_id,
+ ldda_id=ldda_id,
+ library_dataset_id=library_dataset_id,
+ msg=msg,
+ messagetype=messagetype )
+ elif params.get( 'new_info_template_button', False ):
+ # Make sure at least 1 template field is filled in
+ # TODO: Eventually we'll enhance templates to allow for required and optional fields.
+ proceed = False
+ for i in range( int( params.get( 'set_num_fields', 0 ) ) ):
+ elem_name = params.get( 'new_element_name_%i' % i, None )
+ elem_description = params.get( 'new_element_description_%i' % i, None )
+ if elem_name or elem_description:
+ proceed = True
+ break
+ if not proceed:
+ msg = "At least 1 of the fields must be completed."
+ return trans.fill_template( '/library/new_info_template.mako',
+ library_item=library_item,
+ library_item_name=library_item.name,
+ library_item_desc=library_item_desc,
+ num_fields=num_fields,
+ library_id=library_id,
+ folder_id=folder_id,
+ ldda_id=ldda_id,
+ library_dataset_id=library_dataset_id,
+ msg=msg,
+ messagetype=messagetype )
+ # Create template then display edit screen
+ liit = trans.app.model.LibraryItemInfoTemplate()
+ liit.name = util.restore_text( params.get( 'name', '' ) )
+ liit.description = util.restore_text( params.get( 'description', '' ) )
+ liit.flush()
+ # Create template association
+ if folder_id:
+ liit_assoc = trans.app.model.LibraryFolderInfoTemplateAssociation()
+ liit_assoc.folder = trans.app.model.LibraryFolder.get( folder_id )
+ elif ldda_id:
+ liit_assoc = trans.app.model.LibraryDatasetDatasetInfoTemplateAssociation()
+ ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id )
+ liit_assoc.library_dataset_dataset_association = ldda
+ # This response_action method requires a folder_id
+ folder_id = ldda.library_dataset.folder.id
+ elif library_dataset_id:
+ liit_assoc = trans.app.model.LibraryDatasetInfoTemplateAssociation()
+ library_dataset = trans.app.model.LibraryDataset.get( library_dataset_id )
+ liit_assoc.library_dataset = library_dataset
+ # This response_action method requires a folder_id
+ folder_id = library_dataset.folder.id
else:
- if params.get( 'root_folder', False ):
- root_folder = library.root_folder
- root_folder.name = new_name
- root_folder.flush()
- library.name = new_name
- library.description = new_description
- library.flush()
- msg = "Library '%s' has been renamed to '%s'" % ( old_name, new_name )
- return trans.response.send_redirect( web.url_for( controller='library',
- action='library',
- id=id,
- information=True,
- msg=util.sanitize_text( msg ),
- messagetype='done' ) )
- return trans.fill_template( '/library/library_info.mako', library=library, msg=msg, messagetype=messagetype )
- elif action == 'permissions':
- if params.get( 'update_roles_button', False ):
- # The user clicked the Save button on the 'Associate With Roles' form
- permissions = {}
- for k, v in trans.app.model.Library.permitted_actions.items():
- in_roles = [ trans.app.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_all_library_permissions( library, permissions )
- library.refresh()
- msg = "Permissions updated for library '%s'" % library.name
+ # We'll always be sent a library_id
+ liit_assoc = trans.app.model.LibraryInfoTemplateAssociation()
+ liit_assoc.library = trans.app.model.Library.get( library_id )
+ liit_assoc.library_item_info_template = liit
+ liit_assoc.flush()
+ # Create and add elements
+ for i in range( int( params.get( 'set_num_fields', 0 ) ) ):
+ elem_name = params.get( 'new_element_name_%i' % i, None )
+ elem_description = params.get( 'new_element_description_%i' % i, None )
+ if elem_description and not elem_name:
+ # If we have a description but no name, the description will be both
+ # ( a name cannot be empty, but a description can )
+ elem_name = elem_description
+ if elem_name:
+ # Skip any elements that have a missing name
+ liit.add_element( name=elem_name, description=elem_description )
+ msg = "The new information template has been created."
return trans.response.send_redirect( web.url_for( controller='library',
- action='library',
- id=id,
- permissions=True,
+ action=response_action,
+ id=response_id,
+ library_id=library_id,
+ folder_id=folder_id,
msg=util.sanitize_text( msg ),
messagetype='done' ) )
- return trans.fill_template( '/admin/library/manage_library_permissions.mako', library=library, msg=msg, messagetype=messagetype )
- @web.expose
- def library_item_info_template( self, trans, id=None, new_element_count=0, library_id=None,
- folder_id=None, library_dataset_id=None, ldda_id=None, **kwd ):
- params = util.Params( kwd )
- msg = util.restore_text( params.get( 'msg', '' ) )
- messagetype = params.get( 'messagetype', 'done' )
- new_element_count = int( new_element_count )
- liit = None
- if id:
+ return trans.fill_template( '/library/create_info_template.mako',
+ library_item=library_item,
+ library_id=library_id,
+ msg=msg,
+ messagetype=messagetype )
+ elif action == 'edit_template':
try:
- liit = trans.app.model.LibraryItemInfoTemplate.get( id )
+ liit = trans.app.model.LibraryItemInfoTemplate.get( int( id ) )
except:
msg = "Invalid library info template specified, id: %s" % str( id )
- # TODO: this should probably redirect to the library_dataset_dataset_association method...
- return trans.response.send_redirect( web.url_for( controller='admin',
- action='browse_library',
- id=library_id,
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action=response_action,
+ id=response_id,
+ library_id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- if params.get( 'liit_create_button', False ):
- # Create template then display edit screen
- liit = trans.app.model.LibraryItemInfoTemplate()
- liit.name = params.get( 'name', 'unnamed' )
- liit.description = params.get( 'description', '' )
- liit.flush()
- # Create template association
- if library_id:
- liit_assoc = trans.app.model.LibraryInfoTemplateAssociation()
- liit_assoc.library = trans.app.model.Library.get( library_id )
- elif folder_id:
- liit_assoc = trans.app.model.LibraryFolderInfoTemplateAssociation()
- liit_assoc.folder = trans.app.model.LibraryFolder.get( folder_id )
- elif library_dataset_id:
- liit_assoc = trans.app.model.LibraryDatasetInfoTemplateAssociation()
- liit_assoc.library_dataset = trans.app.model.LibraryDataset.get( library_dataset_id )
- elif ldda_id:
- liit_assoc = trans.app.model.LibraryDatasetDatasetInfoTemplateAssociation()
- liit_assoc.library_dataset_dataset_association = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id )
- liit_assoc.library_item_info_template = liit
- liit_assoc.flush()
- # Create and add elements
- for i in range( int( params.get( 'set_element_count', 0 ) ) ):
- elem_name = params.get( 'new_element_name_%i' % i, None )
- elem_description = params.get( 'new_element_description_%i' % i, None )
- # Skip any elements that have a missing name and description
- if not elem_name:
- # If we have a description but no name, the description will be both
- # ( a name cannot be empty, but a description can )
- elem_name = elem_description
- else:
- liit.add_element( name=elem_name, description=elem_description )
- elif params.get( 'liit_edit_button', False ):
- # Save changes to existing attributes, only set name if nonempty/nonNone is passed, but always set description
- name = params.get( 'name', None )
- if name:
- liit.name = name
- liit.description = params.get( 'description', '' )
- liit.flush()
- # Save changes to exisiting elements
- for elem_id in params.get( 'element_ids', [] ):
- liit_element = trans.app.model.LibraryItemInfoTemplateElement.get( elem_id )
- name = params.get( 'element_name_%s' % elem_id, None )
+ if params.get( 'edit_info_template_button', False ):
+ # Save changes to existing attributes, only set name if nonempty/nonNone is passed, but always set description
+ name = params.get( 'name', None )
if name:
- liit_element.name = name
- liit_element.description = params.get( 'element_description_%s' % elem_id, None )
- liit_element.flush()
- # Add new elements
- for i in range( int( params.get( 'set_element_count', 0 ) ) ):
- elem_name = params.get( 'new_element_name_%i' % i, None )
- elem_description = params.get( 'new_element_description_%i' % i, None )
- # Skip any elements that have a missing name and description
- if not elem_name:
- # If we have a description but no name, the description will be both
- # ( a name cannot be empty, but a description can )
- elem_name = elem_description
- if elem_name:
- liit.add_element( name=elem_name, description=elem_description )
- liit.refresh()
- msg = 'Information template %s has been updated' % liit.name
- if library_id:
- library_item_name = trans.app.model.Library.get( library_id ).name
- library_item_desc = 'library'
- elif folder_id:
- library_item_name = trans.app.model.LibraryFolder.get( folder_id ).name
- library_item_desc = 'folder'
- elif library_dataset_id:
- ld = trans.app.model.LibraryDataset.get( library_dataset_id )
- library_item_name = ld.name
- library_item_desc = 'library dataset'
- elif ldda_id:
- library_item_name = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ).name
- library_item_desc = 'library dataset <-> dataset association'
- else:
- library_item_name = 'unknown'
- library_item_desc = ''
- return trans.fill_template( "/library/item_info_template.mako",
- liit=liit,
- new_element_count=new_element_count,
- library_id=library_id,
- library_dataset_id=library_dataset_id,
- ldda_id=ldda_id,
- folder_id=folder_id,
- library_item_name=library_item_name,
- library_item_desc=library_item_desc,
- msg=msg,
- messagetype=messagetype )
+ liit.name = name
+ liit.description = params.get( 'description', '' )
+ liit.flush()
+ # Save changes to exisiting elements
+ for elem_id in params.get( 'element_ids', [] ):
+ liit_element = trans.app.model.LibraryItemInfoTemplateElement.get( elem_id )
+ name = params.get( 'element_name_%s' % elem_id, None )
+ if name:
+ liit_element.name = name
+ liit_element.description = params.get( 'element_description_%s' % elem_id, None )
+ liit_element.flush()
+ # Add new elements
+ for i in range( int( params.get( 'set_num_fields', 0 ) ) ):
+ elem_name = params.get( 'new_element_name_%i' % i, None )
+ elem_description = params.get( 'new_element_description_%i' % i, None )
+ # Skip any elements that have a missing name and description
+ if not elem_name:
+ # If we have a description but no name, the description will be both
+ # ( a name cannot be empty, but a description can )
+ elem_name = elem_description
+ if elem_name:
+ liit.add_element( name=elem_name, description=elem_description )
+ liit.refresh()
+ msg = 'Information template %s has been updated' % liit.name
+ return trans.fill_template( "/library/edit_info_template.mako",
+ liit=liit,
+ num_fields=num_fields,
+ library_id=library_id,
+ library_dataset_id=library_dataset_id,
+ ldda_id=ldda_id,
+ folder_id=folder_id,
+ library_item_name=library_item.name,
+ library_item_desc=library_item_desc,
+ msg=msg,
+ messagetype=messagetype )
@web.expose
- def library_item_info( self, trans, do_action='display', id=None, library_item_id=None, library_item_type=None, library_id=None, **kwd ):
+ def library_item_info( self, trans, library_id, id=None, library_item_id=None, library_item_type=None, **kwd ):
params = util.Params( kwd )
msg = util.restore_text( params.get( 'msg', '' ) )
messagetype = params.get( 'messagetype', 'done' )
+ folder_id = None
if id:
item_info = trans.app.model.LibraryItemInfo.get( id )
else:
item_info = None
if library_item_type == 'library':
library_item = trans.app.model.Library.get( library_item_id )
- error_action = 'browse'
elif library_item_type == 'library_dataset':
library_item = trans.app.model.LibraryDataset.get( library_item_id )
- error_action = 'library_dataset'
elif library_item_type == 'folder':
library_item = trans.app.model.LibraryFolder.get( library_item_id )
elif library_item_type == 'library_dataset_dataset_association':
- error_action = 'folder'
library_item = trans.app.model.LibraryDatasetDatasetAssociation.get( library_item_id )
- error_action = 'library_dataset_dataset_association'
+ # This response_action method requires a folder_id
+ folder_id = library_item.library_dataset.folder.id
else:
library_item_type == None
library_item = None
- if not item_info and not library_item_type:
- msg = "Unable to perform requested action (%s)." % do_action
+ if not library_item:
+ msg = "Invalid library item type ( %s ) specified, id ( %s )" % ( str( library_item_type ), str( library_item_id ) )
return trans.response.send_redirect( web.url_for( controller='library',
- action=error_action,
- id=library_item_id,
+ action='browse_library',
+ id=library_id,
msg=util.sanitize_text( msg ),
messagetype='error' ) )
- #TODO: not sure we need the display option or the display_info.mako template any longer...
- if do_action == 'display':
- return trans.fill_template( "/library/display_info.mako",
- item_info=item_info,
- msg=msg,
- messagetype=messagetype )
- elif do_action == 'new_info':
+ if params.get( 'new_info', False ):
if library_item:
- if trans.app.security_agent.allow_action( trans.user,
- trans.app.security_agent.permitted_actions.LIBRARY_ADD,
- library_item=library_item ):
- if params.get( 'create_new_info_button', False ):
- user = trans.get_user()
- #create new info then send back to make more
- library_item_info_template_id = params.get( 'library_item_info_template_id', None )
- library_item_info_template = trans.app.model.LibraryItemInfoTemplate.get( library_item_info_template_id )
- library_item_info = trans.app.model.LibraryItemInfo()
- library_item_info.library_item_info_template = library_item_info_template
- library_item_info.user = user
- library_item_info.flush()
- trans.app.security_agent.copy_library_permissions( library_item_info_template, library_item_info, user=user )
- for template_element in library_item_info_template.elements:
- info_element_value = params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id), None )
- info_element = trans.app.model.LibraryItemInfoElement()
- info_element.contents = info_element_value
- info_element.library_item_info_template_element = template_element
- info_element.library_item_info = library_item_info
- info_element.flush()
- info_association_class = None
- for item_class, permission_class, info_association_class in trans.app.security_agent.library_item_assocs:
- if isinstance( library_item, item_class ):
- break
- if info_association_class:
- library_item_info_association = info_association_class()
- library_item_info_association.set_library_item( library_item )
- library_item_info_association.library_item_info = library_item_info
- library_item_info_association.user = user
- library_item_info_association.flush()
- else:
- raise 'Invalid class (%s) specified for library_item (%s)' % ( library_item.__class__, library_item.__class__.__name__ )
- # TODO: make sure we don't need to set permissions on the association object.
- msg = 'The information has been saved. You can add more information if necessary.'
- return trans.response.send_redirect( web.url_for( controller='library',
+ if params.get( 'create_new_info_button', False ):
+ library_item_info_template_id = params.get( 'library_item_info_template_id', None )
+ library_item_info_template = trans.app.model.LibraryItemInfoTemplate.get( int( library_item_info_template_id ) )
+ # Make sure at least 1 template field is filled in
+ # TODO: Eventually we'll enhance templates to allow for required and optional fields.
+ proceed = False
+ for template_element in library_item_info_template.elements:
+ if params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id ), None ):
+ proceed = True
+ break
+ if not proceed:
+ msg = "At least 1 of the fields must be completed."
+ return trans.response.send_redirect( web.url_for( controller='admin',
action=library_item_type,
id=library_item.id,
library_id=library_id,
+ folder_id=folder_id,
+ information=True,
msg=util.sanitize_text( msg ),
- messagetype='done' ) )
- return trans.fill_template( "/library/new_info.mako",
- library_item=library_item,
- library_item_type=library_item_type,
- library_id=library_id,
- msg=msg,
- messagetype=messagetype )
- else:
- return trans.show_error_message( "You do not have permission to add information to this library item." )
+ messagetype='error' ) )
+ user = trans.get_user()
+ library_item_info = trans.app.model.LibraryItemInfo()
+ library_item_info.library_item_info_template = library_item_info_template
+ library_item_info.user = user
+ library_item_info.flush()
+ trans.app.security_agent.copy_library_permissions( library_item_info_template, library_item_info )
+ for template_element in library_item_info_template.elements:
+ info_element_value = params.get( "info_element_%s_%s" % ( library_item_info_template.id, template_element.id ), None )
+ info_element = trans.app.model.LibraryItemInfoElement()
+ info_element.contents = info_element_value
+ info_element.library_item_info_template_element = template_element
+ info_element.library_item_info = library_item_info
+ info_element.flush()
+ info_association_class = None
+ for item_class, permission_class, info_association_class in trans.app.security_agent.library_item_assocs:
+ if isinstance( library_item, item_class ):
+ break
+ if info_association_class:
+ library_item_info_association = info_association_class()
+ library_item_info_association.set_library_item( library_item, trans.user )
+ library_item_info_association.library_item_info = library_item_info
+ library_item_info_association.user = user
+ library_item_info_association.flush()
+ else:
+ raise 'Invalid class (%s) specified for library_item (%s)' % ( library_item.__class__, library_item.__class__.__name__ )
+ msg = 'The information has been saved.'
+ return trans.response.send_redirect( web.url_for( controller='library',
+ action=library_item_type,
+ id=library_item.id,
+ library_id=library_id,
+ folder_id=folder_id,
+ information=True,
+ msg=util.sanitize_text( msg ),
+ messagetype='done' ) )
+ return trans.fill_template( "/library/new_info.mako",
+ library_id=library_id,
+ library_item=library_item,
+ library_item_type=library_item_type,
+ msg=msg,
+ messagetype=messagetype )
# TODO: add more functionality -> user's should be able to edit/delete, etc, and create/delete and edit templates
- return trans.fill_template( "/library/display_info.mako",
- item_info=item_info,
- msg="Unable to perform requested action (%s)." % do_action,
- messagetype=messagetype )
diff --git a/lib/galaxy/web/controllers/library_dataset.py b/lib/galaxy/web/controllers/library_dataset.py
new file mode 100644
index 00000000000..e72824420f6
--- /dev/null
+++ b/lib/galaxy/web/controllers/library_dataset.py
@@ -0,0 +1,355 @@
+import os, shutil, urllib, StringIO, re, gzip, tempfile, shutil, zipfile
+from galaxy.web.base.controller import *
+from galaxy import util, jobs
+from galaxy.datatypes import sniff
+from galaxy.security import RBACAgent
+
+log = logging.getLogger( __name__ )
+
+class UploadLibraryDataset( BaseController ):
+ def remove_tempfile( self, filename ):
+ try:
+ os.unlink( filename )
+ except:
+ log.exception( 'failure removing temporary file: %s' % filename )
+ def add_file( self, trans, folder_id, file_obj, name, file_format, dbkey, roles, info='no info', space_to_tab=False, replace_dataset=None ):
+ folder = trans.app.model.LibraryFolder.get( folder_id )
+ data_type = None
+ line_count = 0
+ temp_name = sniff.stream_to_file( file_obj )
+ # See if we have an empty file
+ if not os.path.getsize( temp_name ) > 0:
+ raise BadFileException( "you attempted to upload an empty file." )
+ # See if we have a gzipped file, which, if it passes our restrictions, we'll uncompress on the fly.
+ is_gzipped, is_valid = self.check_gzip( temp_name )
+ if is_gzipped and not is_valid:
+ raise BadFileException( "you attempted to upload an inappropriate file." )
+ elif is_gzipped and is_valid:
+ # We need to uncompress the temp_name file
+ CHUNK_SIZE = 2**20 # 1Mb
+ fd, uncompressed = tempfile.mkstemp()
+ gzipped_file = gzip.GzipFile( temp_name )
+ while 1:
+ try:
+ chunk = gzipped_file.read( CHUNK_SIZE )
+ except IOError:
+ os.close( fd )
+ os.remove( uncompressed )
+ raise BadFileException( 'problem uncompressing gzipped data.' )
+ if not chunk:
+ break
+ os.write( fd, chunk )
+ os.close( fd )
+ gzipped_file.close()
+ # Replace the gzipped file with the decompressed file
+ shutil.move( uncompressed, temp_name )
+ name = name.rstrip( '.gz' )
+ data_type = 'gzip'
+ if not data_type:
+ # See if we have a zip archive
+ is_zipped, is_valid, test_ext = self.check_zip( temp_name )
+ if is_zipped and not is_valid:
+ raise BadFileException( "you attempted to upload an inappropriate file." )
+ elif is_zipped and is_valid:
+ # Currently, we force specific tools to handle this case. We also require the user
+ # to manually set the incoming file_format
+ if ( test_ext == 'ab1' or test_ext == 'scf' ) and file_format != 'binseq.zip':
+ raise BadFileException( "Invalid 'File Format' for archive consisting of binary files - use 'Binseq.zip'." )
+ elif test_ext == 'txt' and file_format != 'txtseq.zip':
+ raise BadFileException( "Invalid 'File Format' for archive consisting of text files - use 'Txtseq.zip'." )
+ if not ( file_format == 'binseq.zip' or file_format == 'txtseq.zip' ):
+ raise BadFileException( "you must manually set the 'File Format' to either 'Binseq.zip' or 'Txtseq.zip' when uploading zip files." )
+ data_type = 'zip'
+ ext = file_format
+ if not data_type:
+ if self.check_binary( temp_name ):
+ ext = file_name.split( "." )[1].strip().lower()
+ if not( ext == 'ab1' or ext == 'scf' ):
+ raise BadFileException( "you attempted to upload an inappropriate file." )
+ if ext == 'ab1' and file_format != 'ab1':
+ raise BadFileException( "you must manually set the 'File Format' to 'Ab1' when uploading ab1 files." )
+ elif ext == 'scf' and file_format != 'scf':
+ raise BadFileException( "you must manually set the 'File Format' to 'Scf' when uploading scf files." )
+ data_type = 'binary'
+ if not data_type:
+ # We must have a text file
+ if self.check_html( temp_name ):
+ raise BadFileException( "you attempted to upload an inappropriate file." )
+ if data_type != 'binary' and data_type != 'zip':
+ if space_to_tab:
+ line_count = sniff.convert_newlines_sep2tabs( temp_name )
+ elif os.stat( temp_name ).st_size < 262144000: # 250MB
+ line_count = sniff.convert_newlines( temp_name )
+ else:
+ if sniff.check_newlines( temp_name ):
+ line_count = sniff.convert_newlines( temp_name )
+ else:
+ line_count = None
+ if file_format == 'auto':
+ ext = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order )
+ else:
+ ext = file_format
+ data_type = ext
+ if info is None:
+ info = 'uploaded %s file' % data_type
+ if file_format == 'auto':
+ data_type = sniff.guess_ext( temp_name, sniff_order=trans.app.datatypes_registry.sniff_order )
+ else:
+ data_type = file_format
+ if replace_dataset:
+ # The replace_dataset param ( when not None ) refers to a LibraryDataset that is being replaced with a new version.
+ # In this case, all of the permissions on the expired LibraryDataset will be applied to the new version.
+ library_dataset = replace_dataset
+ else:
+ # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new
+ # LibraryDataset, and the current user's DefaultUserPermissions will be applied to the associated Dataset.
+ library_dataset = trans.app.model.LibraryDataset( folder=folder, name=name, info=info )
+ library_dataset.flush()
+ trans.app.security_agent.copy_library_permissions( folder, library_dataset )
+ ldda = trans.app.model.LibraryDatasetDatasetAssociation( name=name,
+ info=info,
+ extension=data_type,
+ dbkey=dbkey,
+ library_dataset=library_dataset,
+ create_dataset=True )
+ ldda.flush()
+ # Permissions must be the same on the LibraryDatasetDatasetAssociation and the associated LibraryDataset
+ trans.app.security_agent.copy_library_permissions( library_dataset, ldda )
+ if replace_dataset:
+ # Copy the Dataset level permissions from replace_dataset to the new LibraryDatasetDatasetAssociation.dataset
+ trans.app.security_agent.copy_dataset_permissions( replace_dataset.library_dataset_dataset_association.dataset, ldda.dataset )
+ else:
+ # Copy the current user's DefaultUserPermissions to the new LibraryDatasetDatasetAssociation.dataset
+ trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, trans.app.security_agent.user_get_default_permissions( trans.get_user() ) )
+ folder.add_library_dataset( library_dataset, genome_build=dbkey )
+ library_dataset.library_dataset_dataset_association_id = ldda.id
+ library_dataset.flush()
+ # If roles were selected upon upload, restrict access to the Dataset to those roles
+ if roles:
+ for role in roles:
+ dp = trans.app.model.DatasetPermissions( RBACAgent.permitted_actions.DATASET_ACCESS.action, ldda.dataset, role )
+ dp.flush()
+ shutil.move( temp_name, ldda.dataset.file_name )
+ ldda.state = ldda.states.OK
+ ldda.init_meta()
+ if line_count:
+ try:
+ ldda.set_peek( line_count=line_count )
+ except:
+ ldda.set_peek()
+ else:
+ ldda.set_peek()
+ ldda.set_size()
+ if ldda.missing_meta():
+ ldda.datatype.set_meta( ldda )
+ ldda.flush()
+ return ldda
+ @web.expose
+ def upload_dataset( self, trans, controller, library_id, folder_id, replace_dataset=None, **kwd ):
+ # This method is called from both the admin and library controllers. The replace_dataset param ( when
+ # not None ) refers to a LibraryDataset that is being replaced with a new version.
+ params = util.Params( kwd )
+ msg = util.restore_text( params.get( 'msg', '' ) )
+ messagetype = params.get( 'messagetype', 'done' )
+ dbkey = params.get( 'dbkey', '?' )
+ file_format = params.get( 'file_format', 'auto' )
+ data_file = params.get( 'file_data', '' )
+ url_paste = params.get( 'url_paste', '' )
+ server_dir = params.get( 'server_dir', 'None' )
+ if replace_dataset is not None:
+ replace_id = replace_dataset.id
+ else:
+ replace_id = None
+ if data_file == '' and url_paste == '' and server_dir in [ 'None', '' ]:
+ if trans.app.config.library_import_dir is not None:
+ msg = 'Select a file, enter a URL or Text, or select a server directory.'
+ else:
+ msg = 'Select a file, enter a URL or enter Text.'
+ trans.response.send_redirect( web.url_for( controller=controller,
+ action='library_dataset_dataset_association',
+ library_id=library_id,
+ folder_id=folder_id,
+ replace_id=replace_id,
+ msg=util.sanitize_text( msg ),
+ messagetype='done' ) )
+ space_to_tab = params.get( 'space_to_tab', False )
+ if space_to_tab and space_to_tab not in [ "None", None ]:
+ space_to_tab = True
+ roles = []
+ for role_id in util.listify( params.get( 'roles', [] ) ):
+ roles.append( trans.app.model.Role.get( role_id ) )
+ data_list = []
+ created_ldda_ids = ''
+ if 'filename' in dir( data_file ):
+ file_name = data_file.filename
+ file_name = file_name.split( '\\' )[-1]
+ file_name = file_name.split( '/' )[-1]
+ try:
+ created_ldda = self.add_file( trans,
+ folder_id,
+ data_file.file,
+ file_name,
+ file_format,
+ dbkey,
+ roles,
+ info="uploaded file",
+ space_to_tab=space_to_tab,
+ replace_dataset=replace_dataset )
+ created_ldda_ids = str( created_ldda.id )
+ except Exception, e:
+ log.exception( 'exception in upload_dataset using file_name %s: %s' % ( str( file_name ), str( e ) ) )
+ return self.upload_empty( trans, controller, library_id, "Error:", str( e ) )
+ elif url_paste not in [ None, "" ]:
+ if url_paste.lower().find( 'http://' ) >= 0 or url_paste.lower().find( 'ftp://' ) >= 0:
+ url_paste = url_paste.replace( '\r', '' ).split( '\n' )
+ for line in url_paste:
+ line = line.rstrip( '\r\n' )
+ if line:
+ try:
+ created_ldda = self.add_file( trans,
+ folder_id,
+ urllib.urlopen( line ),
+ line,
+ file_format,
+ dbkey,
+ roles,
+ info="uploaded url",
+ space_to_tab=space_to_tab,
+ replace_dataset=replace_dataset )
+ created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) )
+ except Exception, e:
+ log.exception( 'exception in upload_dataset using url_paste %s' % str( e ) )
+ return self.upload_empty( trans, controller, library_id, "Error:", str( e ) )
+ else:
+ is_valid = False
+ for line in url_paste:
+ line = line.rstrip( '\r\n' )
+ if line:
+ is_valid = True
+ break
+ if is_valid:
+ try:
+ created_ldda = self.add_file( trans,
+ folder_id,
+ StringIO.StringIO( url_paste ),
+ 'Pasted Entry',
+ file_format,
+ dbkey,
+ roles,
+ info="pasted entry",
+ space_to_tab=space_to_tab,
+ replace_dataset=replace_dataset )
+ created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) )
+ except Exception, e:
+ log.exception( 'exception in add_file using StringIO.StringIO( url_paste ) %s' % str( e ) )
+ return self.upload_empty( trans, controller, library_id, "Error:", str( e ) )
+ elif server_dir not in [ None, "", "None" ]:
+ full_dir = os.path.join( trans.app.config.library_import_dir, server_dir )
+ try:
+ files = os.listdir( full_dir )
+ except:
+ log.debug( "Unable to get file list for %s" % full_dir )
+ for file in files:
+ full_file = os.path.join( full_dir, file )
+ if not os.path.isfile( full_file ):
+ continue
+ try:
+ created_ldda = self.add_file( trans,
+ folder_id,
+ open( full_file, 'rb' ),
+ file,
+ file_format,
+ dbkey,
+ roles,
+ info="imported file",
+ space_to_tab=space_to_tab,
+ replace_dataset=replace_dataset )
+ created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( created_ldda.id ) )
+ except Exception, e:
+ log.exception( 'exception in add_file using server_dir %s' % str( e ) )
+ return self.upload_empty( trans, controller, library_id, "Error:", str( e ) )
+ if created_ldda_ids:
+ created_ldda_ids = created_ldda_ids.lstrip( ',' )
+ return created_ldda_ids
+ else:
+ return ''
+ def check_gzip( self, temp_name ):
+ temp = open( temp_name, "U" )
+ magic_check = temp.read( 2 )
+ temp.close()
+ if magic_check != util.gzip_magic:
+ return ( False, False )
+ CHUNK_SIZE = 2**15 # 32Kb
+ gzipped_file = gzip.GzipFile( temp_name )
+ chunk = gzipped_file.read( CHUNK_SIZE )
+ gzipped_file.close()
+ if self.check_html( temp_name, chunk=chunk ) or self.check_binary( temp_name, chunk=chunk ):
+ return( True, False )
+ return ( True, True )
+ def check_zip( self, temp_name ):
+ if not zipfile.is_zipfile( temp_name ):
+ return ( False, False, None )
+ zip_file = zipfile.ZipFile( temp_name, "r" )
+ # Make sure the archive consists of valid files. The current rules are:
+ # 1. Archives can only include .ab1, .scf or .txt files
+ # 2. All file file_formats within an archive must be the same
+ name = zip_file.namelist()[0]
+ test_ext = name.split( "." )[1].strip().lower()
+ if not ( test_ext == 'scf' or test_ext == 'ab1' or test_ext == 'txt' ):
+ return ( True, False, test_ext )
+ for name in zip_file.namelist():
+ ext = name.split( "." )[1].strip().lower()
+ if ext != test_ext:
+ return ( True, False, test_ext )
+ return ( True, True, test_ext )
+ def check_html( self, temp_name, chunk=None ):
+ if chunk is None:
+ temp = open(temp_name, "U")
+ else:
+ temp = chunk
+ regexp1 = re.compile( "]*HREF[^>]+>", re.I )
+ regexp2 = re.compile( "