diff --git a/datatypes_conf.xml.sample b/datatypes_conf.xml.sample index 8c6548f4728..cb49a79ce73 100644 --- a/datatypes_conf.xml.sample +++ b/datatypes_conf.xml.sample @@ -1,211 +1,224 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dist-eggs.ini b/dist-eggs.ini index 93c1a7044e1..d9135c2a0bb 100644 --- a/dist-eggs.ini +++ b/dist-eggs.ini @@ -23,6 +23,9 @@ py2.4-macosx-10.3-fat-ucs2 = medeski.bx.psu.edu /usr/local/bin/python2.4 py2.5-macosx-10.3-fat-ucs2 = medeski.bx.psu.edu /usr/local/bin/python2.5 py2.6-macosx-10.3-fat-ucs2 = medeski.bx.psu.edu /usr/local/bin/python2.6 py2.5-macosx-10.5-i386-ucs2 = lion.bx.psu.edu /usr/bin/python2.5 +py2.4-solaris-2.10-i86pc-ucs2 = thumper.bx.psu.edu /depot/projects/pythons/solaris-2.10-i86pc-ucs2/bin/python2.4 +py2.5-solaris-2.10-i86pc-ucs2 = thumper.bx.psu.edu /depot/projects/pythons/solaris-2.10-i86pc-ucs2/bin/python2.5 +py2.6-solaris-2.10-i86pc-ucs2 = thumper.bx.psu.edu /depot/projects/pythons/solaris-2.10-i86pc-ucs2/bin/python2.6 py2.4-solaris-2.11-i86pc-ucs2 = victory.bx.psu.edu /depot/projects/pythons/solaris-2.11-i86pc-ucs2/bin/python2.4 py2.5-solaris-2.11-i86pc-ucs2 = victory.bx.psu.edu /depot/projects/pythons/solaris-2.11-i86pc-ucs2/bin/python2.5 py2.6-solaris-2.11-i86pc-ucs2 = victory.bx.psu.edu /depot/projects/pythons/solaris-2.11-i86pc-ucs2/bin/python2.6 @@ -47,9 +50,9 @@ py2.4-macosx = py2.4-macosx-10.3-fat-ucs2 py2.5-macosx = py2.5-macosx-10.3-fat-ucs2 py2.5-macosx-10.5-i386-ucs2 py2.6-macosx = py2.6-macosx-10.3-fat-ucs2 macosx = py2.4-macosx py2.5-macosx py2.6-macosx -py2.4-solaris = py2.4-solaris-2.11-i86pc-ucs2 py2.4-solaris-2.10-sun4u-ucs2 -py2.5-solaris = py2.5-solaris-2.11-i86pc-ucs2 py2.5-solaris-2.10-sun4u-ucs2 -py2.6-solaris = py2.6-solaris-2.11-i86pc-ucs2 py2.6-solaris-2.10-sun4u-ucs2 +py2.4-solaris = py2.4-solaris-2.10-i86pc-ucs2 py2.4-solaris-2.11-i86pc-ucs2 py2.4-solaris-2.10-sun4u-ucs2 +py2.5-solaris = py2.5-solaris-2.10-i86pc-ucs2 py2.5-solaris-2.11-i86pc-ucs2 py2.5-solaris-2.10-sun4u-ucs2 +py2.6-solaris = py2.6-solaris-2.10-i86pc-ucs2 py2.6-solaris-2.11-i86pc-ucs2 py2.6-solaris-2.10-sun4u-ucs2 solaris = py2.4-solaris py2.5-solaris py2.6-solaris py2.4-all = py2.4-linux py2.4-macosx py2.4-solaris py2.5-all = py2.5-linux py2.5-macosx py2.5-solaris diff --git a/eggs.ini b/eggs.ini index 016d7603c0d..556e67bdd20 100644 --- a/eggs.ini +++ b/eggs.ini @@ -19,7 +19,7 @@ MySQL_python = 1.2.2 pbs_python = 2.9.4 psycopg2 = 2.0.6 pycrypto = 2.0.1 -pysqlite = 2.3.5 +pysqlite = 2.5.6 python_lzo = 1.08 threadframe = 0.2 guppy = 0.1.8 @@ -57,11 +57,12 @@ boto = 1.8d ; extra version information [tags] psycopg2 = _8.2.6_static -pysqlite = _3.5.4_static +pysqlite = _static MySQL_python = _5.0.67_static python_lzo = _static bx_python = _dev_r4bf1f32e6b76 -GeneTrack = _dev_raa786e9fc131d998e532a1aef39d108850c9e93d +GeneTrack = _dev_e380f21c704218622155b9d230a44b3c9c452524 +SQLAlchemy = _dev_r6498 ; nose = .dev_r7156749efc58 ; source location, necessary for scrambling @@ -73,7 +74,7 @@ MySQL_python = http://superb-west.dl.sourceforge.net/sourceforge/mysql-python/My pbs_python = http://ftp.sara.nl/pub/outgoing/pbs_python-2.9.4.tar.gz psycopg2 = http://initd.org/pub/software/psycopg/PSYCOPG-2-0/psycopg2-2.0.6.tar.gz ftp://ftp-archives.postgresql.org/pub/source/v8.2.6/postgresql-8.2.6.tar.bz2 pycrypto = http://www.amk.ca/files/python/crypto/pycrypto-2.0.1.tar.gz -pysqlite = http://initd.org/pub/software/pysqlite/releases/2.3/2.3.5/pysqlite-2.3.5.tar.gz http://www.sqlite.org/sqlite-source-3_5_4.zip +pysqlite = http://pypi.python.org/packages/source/p/pysqlite/pysqlite-2.5.6.tar.gz python_lzo = http://www.oberhumer.com/opensource/lzo/download/LZO-v1/python-lzo-1.08.tar.gz http://www.oberhumer.com/opensource/lzo/download/LZO-v1/lzo-1.08.tar.gz threadframe = http://www.majid.info/python/threadframe/threadframe-0.2.tar.gz guppy = http://pypi.python.org/packages/source/g/guppy/guppy-0.1.8.tar.gz @@ -82,7 +83,7 @@ Beaker = http://cheeseshop.python.org/packages/source/B/Beaker/Beaker-1.4.tar.gz decorator = http://pypi.python.org/packages/source/d/decorator/decorator-3.1.2.tar.gz docutils = http://downloads.sourceforge.net/docutils/docutils-0.4.tar.gz elementtree = http://effbot.org/downloads/elementtree-1.2.6-20050316.tar.gz -GeneTrack = http://github.com/ialbert/genetrack-central/tarball/aa786e9fc131d998e532a1aef39d108850c9e93d +GeneTrack = http://github.com/ialbert/genetrack-central/tarball/e380f21c704218622155b9d230a44b3c9c452524 lrucache = http://evan.prodromou.name/lrucache/lrucache-0.2.tar.gz Mako = http://www.makotemplates.org/downloads/Mako-0.2.5.tar.gz nose = http://pypi.python.org/packages/source/n/nose/nose-0.11.1.tar.gz @@ -93,7 +94,7 @@ PasteScript = http://cheeseshop.python.org/packages/source/P/PasteScript/PasteSc PSI = http://pypi.python.org/packages/source/P/PSI/PSI-0.3b1.1.tar.gz Routes = http://pypi.python.org/packages/source/R/Routes/Routes-1.11.tar.gz simplejson = http://cheeseshop.python.org/packages/source/s/simplejson/simplejson-1.5.tar.gz -SQLAlchemy = http://pypi.python.org/packages/source/S/SQLAlchemy/SQLAlchemy-0.5.6.tar.gz +SQLAlchemy = http://dist.g2.bx.psu.edu/SQLAlchemy-0.5.6_r6498.tar.bz2 sqlalchemy_migrate = http://pypi.python.org/packages/source/s/sqlalchemy-migrate/sqlalchemy-migrate-0.5.4.tar.gz Tempita = http://pypi.python.org/packages/source/T/Tempita/Tempita-0.1.tar.gz twill = http://darcs.idyll.org/~t/projects/twill-0.9.tar.gz diff --git a/lib/galaxy/datatypes/binary.py b/lib/galaxy/datatypes/binary.py new file mode 100644 index 00000000000..7a61bc4222b --- /dev/null +++ b/lib/galaxy/datatypes/binary.py @@ -0,0 +1,156 @@ +""" +Binary classes +""" + +import data, logging, binascii +from galaxy.datatypes.metadata import MetadataElement +from galaxy.datatypes import metadata +from galaxy.datatypes.sniff import * +from urllib import urlencode, quote_plus +import zipfile +import os, subprocess, tempfile + +log = logging.getLogger(__name__) + +sniffable_binary_formats = [ 'sff' ] +# Currently these supported binary data types must be manually set on upload +unsniffable_binary_formats = [ 'ab1', 'scf' ] + +class Binary( data.Data ): + """Binary data""" + def set_peek( self, dataset ): + """Set the peek and blurb text""" + if not dataset.dataset.purged: + dataset.peek = 'binary data' + dataset.blurb = 'data' + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + +class Ab1( Binary ): + """Class describing an ab1 binary sequence file""" + file_ext = "ab1" + def set_peek( self, dataset ): + if not dataset.dataset.purged: + export_url = "/history_add_to?" + urlencode( {'history_id':dataset.history_id,'ext':'ab1','name':'ab1 sequence','info':'Sequence file','dbkey':dataset.dbkey} ) + dataset.peek = "Binary ab1 sequence file" + dataset.blurb = data.nice_size( dataset.get_size() ) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + def display_peek( self, dataset ): + try: + return dataset.peek + except: + return "Binary ab1 sequence file (%s)" % ( data.nice_size( dataset.get_size() ) ) + +class Bam( Binary ): + """Class describing a BAM binary file""" + file_ext = "bam" + MetadataElement( name="bam_index", desc="BAM Index File", param=metadata.FileParameter, readonly=True, no_value=None, visible=False, optional=True ) + def init_meta( self, dataset, copy_from=None ): + Binary.init_meta( self, dataset, copy_from=copy_from ) + def set_meta( self, dataset, overwrite = True, **kwd ): + """ + Sets index for BAM file. + """ + index_file = dataset.metadata.bam_index + if not index_file: + index_file = dataset.metadata.spec['bam_index'].param.new_file( dataset = dataset ) + tmp_dir = tempfile.gettempdir() + tmpf1 = tempfile.NamedTemporaryFile( dir=tmp_dir ) + tmpf1bai = '%s.bai' % tmpf1.name + try: + os.system( 'cd %s' % tmp_dir ) + os.system( 'cp %s %s' % ( dataset.file_name, tmpf1.name ) ) + os.system( 'samtools index %s' % tmpf1.name ) + os.system( 'cp %s %s' % ( tmpf1bai, index_file.file_name ) ) + except Exception, ex: + sys.stderr.write( 'There was a problem creating the index for the BAM file\n%s\n' + str( ex ) ) + tmpf1.close() + if os.path.exists( tmpf1bai ): + os.remove( tmpf1bai ) + dataset.metadata.bam_index = index_file + def set_peek( self, dataset ): + if not dataset.dataset.purged: + export_url = "/history_add_to?" + urlencode( {'history_id':dataset.history_id,'ext':'bam','name':'bam alignments','info':'Alignments file','dbkey':dataset.dbkey} ) + dataset.peek = "Binary bam alignments file" + dataset.blurb = data.nice_size( dataset.get_size() ) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + def display_peek( self, dataset ): + try: + return dataset.peek + except: + return "Binary bam alignments file (%s)" % ( data.nice_size( dataset.get_size() ) ) + def get_mime( self ): + """Returns the mime type of the datatype""" + return 'application/octet-stream' + +class Binseq( Binary ): + """Class describing a zip archive of binary sequence files""" + file_ext = "binseq.zip" + def set_peek( self, dataset ): + if not dataset.dataset.purged: + zip_file = zipfile.ZipFile( dataset.file_name, "r" ) + num_files = len( zip_file.namelist() ) + dataset.peek = "Archive of %s binary sequence files" % ( str( num_files ) ) + dataset.blurb = data.nice_size( dataset.get_size() ) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + def display_peek( self, dataset ): + try: + return dataset.peek + except: + return "Binary sequence file archive (%s)" % ( data.nice_size( dataset.get_size() ) ) + def get_mime( self ): + """Returns the mime type of the datatype""" + return 'application/zip' + +class Scf( Binary ): + """Class describing an scf binary sequence file""" + file_ext = "scf" + def set_peek( self, dataset ): + if not dataset.dataset.purged: + export_url = "/history_add_to?" + urlencode({'history_id':dataset.history_id,'ext':'scf','name':'scf sequence','info':'Sequence file','dbkey':dataset.dbkey}) + dataset.peek = "Binary scf sequence file" + dataset.blurb = data.nice_size( dataset.get_size() ) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + def display_peek( self, dataset ): + try: + return dataset.peek + except: + return "Binary scf sequence file (%s)" % ( data.nice_size( dataset.get_size() ) ) + +class Sff( Binary ): + """ Standard Flowgram Format (SFF) """ + file_ext = "sff" + def __init__( self, **kwd ): + Binary.__init__( self, **kwd ) + def sniff( self, filename ): + # The first 4 bytes of any sff file is '.sff', and the file is binary. For details + # about the format, see http://www.ncbi.nlm.nih.gov/Traces/trace.cgi?cmd=show&f=formats&m=doc&s=format + try: + header = open( filename ).read(4) + if binascii.b2a_hex( header ) == binascii.hexlify( '.sff' ): + return True + return False + except Exception, e: + return False + def set_peek( self, dataset ): + if not dataset.dataset.purged: + export_url = "/history_add_to?" + urlencode( {'history_id':dataset.history_id,'ext':'sff','name':'sff file','info':'sff file','dbkey':dataset.dbkey} ) + dataset.peek = "Binary sff file" + dataset.blurb = data.nice_size( dataset.get_size() ) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + def display_peek( self, dataset ): + try: + return dataset.peek + except: + return "Binary sff file (%s)" % ( data.nice_size( dataset.get_size() ) ) diff --git a/lib/galaxy/datatypes/data.py b/lib/galaxy/datatypes/data.py index 539aded117b..531799fbb0e 100644 --- a/lib/galaxy/datatypes/data.py +++ b/lib/galaxy/datatypes/data.py @@ -1,4 +1,4 @@ -import logging, os, sys, time, tempfile, binascii +import logging, os, sys, time, tempfile from galaxy import util from galaxy.util.odict import odict from galaxy.util.bunch import Bunch @@ -40,20 +40,18 @@ class Data( object ): """ __metaclass__ = DataMeta - - """Add metadata elements""" + # Add metadata elements MetadataElement( name="dbkey", desc="Database/Build", default="?", param=metadata.DBKeyParameter, multiple=False, no_value="?" ) - - """Stores the set of display applications, and viewing methods, supported by this datatype """ + # Stores the set of display applications, and viewing methods, supported by this datatype supported_display_apps = {} - - """If False, the peek is regenerated whenever a dataset of this type is copied""" + # If False, the peek is regenerated whenever a dataset of this type is copied copy_safe_peek = True - - is_binary = True #The dataset contains binary data --> do not space_to_tab or convert newlines, etc. Allow binary file uploads of this type when True. - - allow_datatype_change = True #Allow user to change between this datatype and others. If False, this datatype cannot be changed from or into. - + # The dataset contains binary data --> do not space_to_tab or convert newlines, etc. + # Allow binary file uploads of this type when True. + is_binary = True + # Allow user to change between this datatype and others. If False, this datatype + # cannot be changed from or into. + allow_datatype_change = True #Composite datatypes composite_type = None composite_files = odict() @@ -162,6 +160,11 @@ class Data( object ): info = info.replace( '\r', '
' ) if info.find( '\n' ) >= 0: info = info.replace( '\n', '
' ) + + # Convert to unicode to display non-ascii characters. + if type( info ) is not unicode: + info = unicode( info, 'utf-8') + return info except: return "info unavailable" @@ -270,8 +273,6 @@ class Data( object ): def add_composite_file( self, name, **kwds ): #self.composite_files = self.composite_files.copy() self.composite_files[ name ] = self.__new_composite_file( name, **kwds ) - - def __substitute_composite_key( self, key, composite_file, dataset = None ): if composite_file.substitute_name_with_metadata: if dataset: @@ -303,7 +304,6 @@ class Data( object ): return files def generate_auto_primary_file( self, dataset = None ): raise Exception( "generate_auto_primary_file is not implemented for this datatype." ) - @property def has_resolution(self): return False @@ -364,23 +364,37 @@ class Text( Data ): dataset.peek = 'file does not exist' dataset.blurb = 'file purged from disk' -class Binary( Data ): - """Binary data""" +class Txtseq( Data ): + """Class describing a zip archive of text sequence files""" + file_ext = "txtseq.zip" def set_peek( self, dataset ): - """Set the peek and blurb text""" if not dataset.dataset.purged: - dataset.peek = 'binary data' - dataset.blurb = 'data' + zip_file = zipfile.ZipFile( dataset.file_name, "r" ) + num_files = len( zip_file.namelist() ) + dataset.peek = "Archive of %s text sequence files" % ( str( num_files ) ) + dataset.blurb = data.nice_size( dataset.get_size() ) else: dataset.peek = 'file does not exist' dataset.blurb = 'file purged from disk' + def display_peek(self, dataset): + try: + return dataset.peek + except: + return "Text sequence file archive (%s)" % ( data.nice_size( dataset.get_size() ) ) + def get_mime(self): + """Returns the mime type of the datatype""" + return 'application/zip' + +class Newick( Text ): + pass + +# ------------- Utility methods -------------- def get_test_fname( fname ): """Returns test data filename""" path, name = os.path.split(__file__) full_path = os.path.join( path, 'test', fname ) return full_path - def nice_size(size): """ Returns a readably formatted string with the size @@ -406,7 +420,6 @@ def nice_size(size): out = "%.1f %s" % (size, word) return out return '??? bytes' - def get_file_peek( file_name, is_multi_byte=False, WIDTH=256, LINE_COUNT=5 ): """ Returns the first LINE_COUNT lines wrapped to WIDTH @@ -443,7 +456,6 @@ def get_file_peek( file_name, is_multi_byte=False, WIDTH=256, LINE_COUNT=5 ): else: text = unicode( '\n'.join( lines ), 'utf-8' ) return text - def get_line_count(file_name): """Returns the number of lines in a file that are neither null nor comments""" count = 0 @@ -452,38 +464,3 @@ def get_line_count(file_name): if line and line[0] != '#': count += 1 return count - -class Newick( Text ): - pass - -class Sff( Binary ): - """ Standard Flowgram Format (SFF) """ - file_ext = "sff" - def __init__( self, **kwd ): - Binary.__init__(self, **kwd) - def init_meta( self, dataset, copy_from=None ): - Binary.init_meta( self, dataset, copy_from=copy_from ) - def sniff( self, filename ): - ''' - The first 4 bytes of any sff file is '.sff' - - >>> fname = get_test_fname( '1.sff' ) - >>> Sff().sniff( fname ) - True - ''' - header = open( filename ).read(4) - if binascii.b2a_hex( header ) == binascii.hexlify( '.sff' ): - return True - return False - def set_peek( self, dataset ): - if not dataset.dataset.purged: - dataset.peek = "Binary sff file" - dataset.blurb = nice_size( dataset.get_size() ) - else: - dataset.peek = 'file does not exist' - dataset.blurb = 'file purged from disk' - def display_peek(self, dataset): - try: - return dataset.peek - except: - return "sff file (%s)" % ( nice_size( dataset.get_size() ) ) diff --git a/lib/galaxy/datatypes/genetics.py b/lib/galaxy/datatypes/genetics.py index 0e0b739f9eb..565fcddbbc7 100644 --- a/lib/galaxy/datatypes/genetics.py +++ b/lib/galaxy/datatypes/genetics.py @@ -1,6 +1,5 @@ """ rgenetics datatypes -Use at your peril Ross Lazarus for the rgenetics and galaxy projects @@ -11,7 +10,6 @@ subsequent row values are all numeric ! Will fail if any non numeric (eg '+' or ross lazarus for rgenetics august 20 2007 """ - import logging, os, sys, time, tempfile, shutil, string, glob import data from galaxy import util @@ -26,8 +24,7 @@ from galaxy.datatypes.images import Html from galaxy.datatypes.interval import Interval from galaxy.util.hash_util import * -gal_Log = logging.getLogger(__name__) -verbose = False +log = logging.getLogger(__name__) class GenomeGraphs(Interval): @@ -48,10 +45,8 @@ class GenomeGraphs(Interval): """Initialize datatype, by adding GBrowse display app""" Interval.__init__(self, **kwd) self.add_display_app ( 'ucsc', 'display at UCSC', 'as_ucsc_display_file', 'ucsc_links' ) - def as_ucsc_display_file( self, dataset, **kwd ): return open( dataset.file_name ) - def set_meta( self, dataset, overwrite = True, **kwd ): i = 0 for i, line in enumerate( file ( dataset.file_name ) ): @@ -66,7 +61,6 @@ class GenomeGraphs(Interval): except: pass Interval.set_meta( self, dataset, overwrite = overwrite, skip = i ) - def make_html_table( self, dataset, skipchars=[] ): """Create HTML table, used for displaying peek""" out = [''] @@ -82,7 +76,6 @@ class GenomeGraphs(Interval): except Exception, exc: out = "Can't create peek %s" % exc return out - def get_estimated_display_viewport( self, dataset ): """ Return a chrom, start, stop tuple for viewing a file. There are slight differences between gff 2 and gff 3 @@ -118,7 +111,6 @@ class GenomeGraphs(Interval): return ( seqid, str( start ), str( stop ) ) else: return ( '', '', '' ) - def gbrowse_links( self, dataset, type, app, base_url ): ret_val = [] if dataset.has_data: @@ -132,7 +124,6 @@ class GenomeGraphs(Interval): link = "%s?start=%s&stop=%s&ref=%s&dbkey=%s" % ( site_url, start, stop, seqid, dataset.dbkey ) ret_val.append( ( site_name, link ) ) return ret_val - def ucsc_links( self, dataset, type, app, base_url ): ret_val = [] if dataset.has_data: @@ -160,10 +151,8 @@ class GenomeGraphs(Interval): link = '%s?redirect_url=%s&display_url=%s' % ( internal_url, redirect_url, display_url ) ret_val.append( (site_name, link) ) else: - gal_Log.debug('@@@ gg ucsc_links - no viewport_tuple') + log.debug('@@@ gg ucsc_links - no viewport_tuple') return ret_val - - def sniff( self, filename ): """ Determines whether the file is in gff format @@ -202,20 +191,17 @@ class GenomeGraphs(Interval): except: return False - - class rgTabList(Tabular): - """ for sampleid and for featureid lists of exclusions or inclusions in the clean tool + """ + for sampleid and for featureid lists of exclusions or inclusions in the clean tool featureid subsets on statistical criteria -> specialized display such as gg """ file_ext = "rgTList" - def __init__(self, **kwd): """Initialize featurelistt datatype""" Tabular.__init__( self, **kwd ) self.column_names = [] - def make_html_table( self, dataset, skipchars=[] ): """Create HTML table, used for displaying peek""" out = ['
'] @@ -236,23 +222,24 @@ class rgTabList(Tabular): out = "Can't create peek %s" % exc return out - class rgSampleList(rgTabList): - """ for sampleid exclusions or inclusions in the clean tool - output from QC eg excess het, gender error, ibd pair member,eigen outlier,excess mendel errors,... - since they can be uploaded, should be flexible - but they are persistent at least - same infrastructure for expression? + """ + for sampleid exclusions or inclusions in the clean tool + output from QC eg excess het, gender error, ibd pair member,eigen outlier,excess mendel errors,... + since they can be uploaded, should be flexible + but they are persistent at least + same infrastructure for expression? """ file_ext = "rgSList" def __init__(self, **kwd): - """Initialize samplelist datatype""" + """ + Initialize samplelist datatype + """ rgTabList.__init__( self, **kwd ) self.column_names[0] = 'FID' self.column_names[1] = 'IID' # this is what Plink wants as at 2009 - def sniff(self,filename): """ """ @@ -264,10 +251,11 @@ class rgSampleList(rgTabList): return False class rgFeatureList( rgTabList ): - """ for featureid lists of exclusions or inclusions in the clean tool - output from QC eg low maf, high missingness, bad hwe in controls, excess mendel errors,... - featureid subsets on statistical criteria -> specialized display such as gg - same infrastructure for expression? + """ + for featureid lists of exclusions or inclusions in the clean tool + output from QC eg low maf, high missingness, bad hwe in controls, excess mendel errors,... + featureid subsets on statistical criteria -> specialized display such as gg + same infrastructure for expression? """ file_ext = "rgFList" @@ -276,26 +264,23 @@ class rgFeatureList( rgTabList ): rgTabList.__init__( self, **kwd ) for i,s in enumerate(['#FeatureId', 'Chr', 'Genpos', 'Mappos']): self.column_names[i] = s - class Rgenetics(Html): - """class to use for rgenetics""" - - MetadataElement( name="base_name", desc="base name for all transformed versions of this genetic dataset", default="rgenetics", - readonly=True, set_in_upload=True) + """ + class to use for rgenetics + """ + MetadataElement( name="base_name", desc="base name for all transformed versions of this genetic dataset", default="rgenetics", readonly=True, set_in_upload=True) composite_type = 'auto_primary_file' allow_datatype_change = False file_ext = 'rgenetics' - def missing_meta( self, dataset=None, **kwargs): """Checks for empty meta values""" for key, value in dataset.metadata.items(): if not value: return True return False - def generate_primary_file( self, dataset = None ): rval = ['Rgenetics Galaxy Composite Dataset

'] rval.append('

This composite dataset is composed of the following files:

' ) return "\n".join( rval ) - def regenerate_primary_file(self,dataset): - """cannot do this until we are setting metadata + """ + cannot do this until we are setting metadata """ def fix(oldpath,newbase): old,e = os.path.splitext(oldpath) @@ -332,30 +317,25 @@ class Rgenetics(Html): f.write("\n".join( rval )) f.write('\n') f.close() - def set_meta( self, dataset, **kwd ): - - """for lped/pbed eg - + """ + for lped/pbed eg """ if kwd.get('overwrite') == False: - if verbose: - gal_Log.debug('@@@ rgenetics set_meta called with overwrite = False') + #log.debug('@@@ rgenetics set_meta called with overwrite = False') return True try: efp = dataset.extra_files_path except: - if verbose: - gal_Log.debug('@@@rgenetics set_meta failed %s - dataset %s has no efp ?' % (sys.exc_info()[0], dataset.name)) + #log.debug('@@@rgenetics set_meta failed %s - dataset %s has no efp ?' % (sys.exc_info()[0], dataset.name)) return False try: - flist = os.listdir(efp) - except: - if verbose: gal_Log.debug('@@@rgenetics set_meta failed %s - dataset %s has no efp ?' % (sys.exc_info()[0],dataset.name)) + flist = os.listdir(efp) + except: + #log.debug('@@@rgenetics set_meta failed %s - dataset %s has no efp ?' % (sys.exc_info()[0],dataset.name)) return False if len(flist) == 0: - if verbose: - gal_Log.debug('@@@rgenetics set_meta failed - %s efp %s is empty?' % (dataset.name,efp)) + #log.debug('@@@rgenetics set_meta failed - %s efp %s is empty?' % (dataset.name,efp)) return False bn = None for f in flist: @@ -372,9 +352,9 @@ class Rgenetics(Html): dataset.blurb = 'Composite file - Rgenetics Galaxy toolkit' return True - class SNPMatrix(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="snpmatrix" @@ -385,9 +365,9 @@ class SNPMatrix(Rgenetics): else: dataset.peek = 'file does not exist' dataset.blurb = 'file purged from disk' - def sniff(self,filename): - """ need to check the file header hex code + """ + need to check the file header hex code """ infile = open(dataset.file_name, "b") head = infile.read(16) @@ -397,9 +377,9 @@ class SNPMatrix(Rgenetics): else: return True - class Lped(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="lped" @@ -408,9 +388,9 @@ class Lped(Rgenetics): self.add_composite_file( '%s.ped', description = 'Pedigree File', substitute_name_with_metadata = 'base_name', is_binary = True ) self.add_composite_file( '%s.map', description = 'Map File', substitute_name_with_metadata = 'base_name', is_binary = True ) - class Pphe(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="pphe" @@ -418,14 +398,15 @@ class Pphe(Rgenetics): Rgenetics.__init__(self, **kwd) self.add_composite_file( '%s.pphe', description = 'Plink Phenotype File', substitute_name_with_metadata = 'base_name' ) - class Lmap(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="lmap" class Fphe(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="fphe" @@ -434,7 +415,8 @@ class Fphe(Rgenetics): self.add_composite_file( '%s.fphe', description = 'FBAT Phenotype File', substitute_name_with_metadata = 'base_name' ) class Phe(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="phe" @@ -442,10 +424,9 @@ class Phe(Rgenetics): Rgenetics.__init__(self, **kwd) self.add_composite_file( '%s.phe', description = 'Phenotype File', substitute_name_with_metadata = 'base_name' ) - - class Fped(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="fped" @@ -453,9 +434,9 @@ class Fped(Rgenetics): Rgenetics.__init__(self, **kwd) self.add_composite_file( '%s.fped', description = 'FBAT format pedfile', substitute_name_with_metadata = 'base_name' ) - class Pbed(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="pbed" @@ -466,7 +447,8 @@ class Pbed(Rgenetics): self.add_composite_file( '%s.fam', substitute_name_with_metadata = 'base_name', is_binary = True ) class Eigenstratgeno(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="eigenstratgeno" @@ -475,11 +457,10 @@ class Eigenstratgeno(Rgenetics): self.add_composite_file( '%s.eigenstratgeno', substitute_name_with_metadata = 'base_name', is_binary = True ) self.add_composite_file( '%s.ind', substitute_name_with_metadata = 'base_name', is_binary = True ) self.add_composite_file( '%s.map', substitute_name_with_metadata = 'base_name', is_binary = True ) - - class Eigenstratpca(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="eigenstratpca" @@ -487,22 +468,21 @@ class Eigenstratpca(Rgenetics): Rgenetics.__init__(self, **kwd) self.add_composite_file( '%s.eigenstratpca', description = 'Eigenstrat PCA file', substitute_name_with_metadata = 'base_name' ) - class Snptest(Rgenetics): - """fake class to distinguish different species of Rgenetics data collections + """ + fake class to distinguish different species of Rgenetics data collections """ file_ext="snptest" - class Pheno(Tabular): """ base class for pheno files """ file_ext = 'pheno' - class RexpBase( Html ): - """base class for BioC data structures in Galaxy + """ + base class for BioC data structures in Galaxy must be constructed with the pheno data in place since that goes into the metadata for each instance """ @@ -518,18 +498,16 @@ class RexpBase( Html ): composite_type = 'auto_primary_file' allow_datatype_change = False - def __init__( self, **kwd ): Html.__init__(self,**kwd) self.add_composite_file( '%s.pheno', description = 'Phenodata tab text file', substitute_name_with_metadata = 'base_name', is_binary=True) - def generate_primary_file( self, dataset = None ): - """ This is called only at upload to write the html file + """ + This is called only at upload to write the html file cannot rename the datasets here - they come with the default unfortunately """ return 'AutoGenerated Primary File for Composite Dataset' - def get_phecols(self, phenolist=[], maxConc=20): """ sept 2009: cannot use whitespace to split - make a more complex structure here @@ -555,7 +533,7 @@ class RexpBase( Html ): else: for col,code in enumerate(row): # keep column order correct if col >= totcols: - gal_Log.warning('### get_phecols error in pheno file - row %d col %d (%s) longer than header %s' % (nrows, col, row, head)) + log.warning('### get_phecols error in pheno file - row %d col %d (%s) longer than header %s' % (nrows, col, row, head)) else: concordance[col].setdefault(code,0) # first one is zero concordance[col][code] += 1 @@ -601,10 +579,9 @@ class RexpBase( Html ): res = [('no usable phenotype columns found',[('?',0),]),] return res - - def get_pheno(self,dataset): - """expects a .pheno file in the extra_files_dir - ugh + """ + expects a .pheno file in the extra_files_dir - ugh note that R is wierd and adds the row.name in the header so the columns are all wrong - unless you tell it not to. A file can be written as @@ -620,11 +597,12 @@ class RexpBase( Html ): else: p = [] return '\n'.join(p) - def set_peek( self, dataset ): - """expects a .pheno file in the extra_files_dir - ugh + """ + expects a .pheno file in the extra_files_dir - ugh note that R is wierd and does not include the row.name in - the header. why?""" + the header. why? + """ if not dataset.dataset.purged: pp = os.path.join(dataset.extra_files_path,'%s.pheno' % dataset.metadata.base_name) try: @@ -636,17 +614,14 @@ class RexpBase( Html ): else: dataset.peek = 'file does not exist\n' dataset.blurb = 'file purged from disk' - def get_peek( self, dataset ): - """expects a .pheno file in the extra_files_dir - ugh - """ + """expects a .pheno file in the extra_files_dir - ugh""" pp = os.path.join(dataset.extra_files_path,'%s.pheno' % dataset.metadata.base_name) try: p = file(pp,'r').readlines() except: p = ['##failed to find %s' % pp] return ''.join(p[:5]) - def get_file_peek(self,filename): """ can't really peek at a filename - need the extra_files_path and such? @@ -657,7 +632,6 @@ class RexpBase( Html ): except: pass return ''.join(h[:5]) - def regenerate_primary_file(self,dataset): """cannot do this until we are setting metadata """ @@ -672,24 +646,19 @@ class RexpBase( Html ): f.write("\n".join( rval )) f.write('\n') f.close() - - """Add metadata elements""" def init_meta( self, dataset, copy_from=None ): + """Add metadata elements""" if copy_from: dataset.metadata = copy_from.metadata - def set_meta( self, dataset, **kwd ): - """ NOTE we apply the tabular machinary to the phenodata extracted from a BioC eSet or affybatch. - """ try: flist = os.listdir(dataset.extra_files_path) except: - if verbose: - gal_Log.debug('@@@rexpression set_meta failed - no dataset?') + #log.debug('@@@rexpression set_meta failed - no dataset?') return False bn = None for f in flist: @@ -727,7 +696,6 @@ class RexpBase( Html ): if not dataset.blurb: dataset.blurb = 'R loadable BioC expression object for the Rexpression Galaxy toolkit' return True - def make_html_table( self, pp='nothing supplied from peek\n'): """Create HTML table, used for displaying peek""" out = ['
',] @@ -750,20 +718,16 @@ class RexpBase( Html ): except Exception, exc: out = "Can't create html table %s" % str( exc ) return out - def display_peek( self, dataset ): """Returns formatted html of peek""" out=self.make_html_table(dataset.peek) return out - def get_mime(self): """Returns the mime type of the datatype""" return 'text/html' - class Affybatch( RexpBase ): """derived class for BioC data structures in Galaxy """ - file_ext = "affybatch" def __init__( self, **kwd ): @@ -780,7 +744,6 @@ class Eset( RexpBase ): self.add_composite_file( '%s.eset', description = 'ESet R object saved to file', substitute_name_with_metadata = 'base_name', is_binary = True ) - class MAlist( RexpBase ): """derived class for BioC data structures in Galaxy """ file_ext = "malist" @@ -790,9 +753,6 @@ class MAlist( RexpBase ): self.add_composite_file( '%s.malist', description = 'MAlist R object saved to file', substitute_name_with_metadata = 'base_name', is_binary = True ) - if __name__ == '__main__': import doctest, sys doctest.testmod(sys.modules[__name__]) - - diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 17d0f659a8d..a5e17965446 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -13,82 +13,6 @@ import os, subprocess, tempfile log = logging.getLogger(__name__) -class Ab1( data.Data ): - """Class describing an ab1 binary sequence file""" - file_ext = "ab1" - def set_peek( self, dataset ): - if not dataset.dataset.purged: - export_url = "/history_add_to?" + urlencode({'history_id':dataset.history_id,'ext':'ab1','name':'ab1 sequence','info':'Sequence file','dbkey':dataset.dbkey}) - dataset.peek = "Binary ab1 sequence file" - dataset.blurb = data.nice_size( dataset.get_size() ) - else: - dataset.peek = 'file does not exist' - dataset.blurb = 'file purged from disk' - def display_peek(self, dataset): - try: - return dataset.peek - except: - return "Binary ab1 sequence file (%s)" % ( data.nice_size( dataset.get_size() ) ) - -class Scf( data.Data ): - """Class describing an scf binary sequence file""" - file_ext = "scf" - def set_peek( self, dataset ): - if not dataset.dataset.purged: - export_url = "/history_add_to?" + urlencode({'history_id':dataset.history_id,'ext':'scf','name':'scf sequence','info':'Sequence file','dbkey':dataset.dbkey}) - dataset.peek = "Binary scf sequence file" - dataset.blurb = data.nice_size( dataset.get_size() ) - else: - dataset.peek = 'file does not exist' - dataset.blurb = 'file purged from disk' - def display_peek(self, dataset): - try: - return dataset.peek - except: - return "Binary scf sequence file (%s)" % ( data.nice_size( dataset.get_size() ) ) - -class Binseq( data.Data ): - """Class describing a zip archive of binary sequence files""" - file_ext = "binseq.zip" - def set_peek( self, dataset ): - if not dataset.dataset.purged: - zip_file = zipfile.ZipFile( dataset.file_name, "r" ) - num_files = len( zip_file.namelist() ) - dataset.peek = "Archive of %s binary sequence files" % ( str( num_files ) ) - dataset.blurb = data.nice_size( dataset.get_size() ) - else: - dataset.peek = 'file does not exist' - dataset.blurb = 'file purged from disk' - def display_peek(self, dataset): - try: - return dataset.peek - except: - return "Binary sequence file archive (%s)" % ( data.nice_size( dataset.get_size() ) ) - def get_mime(self): - """Returns the mime type of the datatype""" - return 'application/zip' - -class Txtseq( data.Data ): - """Class describing a zip archive of text sequence files""" - file_ext = "txtseq.zip" - def set_peek( self, dataset ): - if not dataset.dataset.purged: - zip_file = zipfile.ZipFile( dataset.file_name, "r" ) - num_files = len( zip_file.namelist() ) - dataset.peek = "Archive of %s text sequence files" % ( str( num_files ) ) - dataset.blurb = data.nice_size( dataset.get_size() ) - else: - dataset.peek = 'file does not exist' - dataset.blurb = 'file purged from disk' - def display_peek(self, dataset): - try: - return dataset.peek - except: - return "Text sequence file archive (%s)" % ( data.nice_size( dataset.get_size() ) ) - def get_mime(self): - """Returns the mime type of the datatype""" - return 'application/zip' - class Image( data.Data ): """Class describing an image""" def set_peek( self, dataset ): @@ -236,47 +160,3 @@ class Laj( data.Text ): return dataset.peek except: return "peek unavailable" - -class Bam( data.Binary ): - """Class describing a BAM binary file""" - file_ext = "bam" - MetadataElement( name="bam_index", desc="BAM Index File", param=metadata.FileParameter, readonly=True, no_value=None, visible=False, optional=True ) - def init_meta( self, dataset, copy_from=None ): - data.Binary.init_meta( self, dataset, copy_from=copy_from ) - def set_meta( self, dataset, overwrite = True, **kwd ): - """ - Sets index for BAM file. - """ - index_file = dataset.metadata.bam_index - if not index_file: - index_file = dataset.metadata.spec['bam_index'].param.new_file( dataset = dataset ) - tmp_dir = tempfile.gettempdir() - tmpf1 = tempfile.NamedTemporaryFile(dir=tmp_dir) - tmpf1bai = '%s.bai' % tmpf1.name - try: - os.system('cd %s' % tmp_dir) - os.system('cp %s %s' % (dataset.file_name, tmpf1.name)) - os.system('samtools index %s' % tmpf1.name) - os.system('cp %s %s' % (tmpf1bai, index_file.file_name)) - except Exception, ex: - sys.stderr.write('There was a problem creating the index for the BAM file\n%s\n' + str(ex)) - tmpf1.close() - if os.path.exists(tmpf1bai): - os.remove(tmpf1bai) - dataset.metadata.bam_index = index_file - def set_peek( self, dataset ): - if not dataset.dataset.purged: - export_url = "/history_add_to?" + urlencode({'history_id':dataset.history_id,'ext':'bam','name':'bam alignments','info':'Alignments file','dbkey':dataset.dbkey}) - dataset.peek = "Binary bam alignments file" - dataset.blurb = data.nice_size( dataset.get_size() ) - else: - dataset.peek = 'file does not exist' - dataset.blurb = 'file purged from disk' - def display_peek(self, dataset): - try: - return dataset.peek - except: - return "Binary bam alignments file (%s)" % ( data.nice_size( dataset.get_size() ) ) - def get_mime(self): - """Returns the mime type of the datatype""" - return 'application/octet-stream' diff --git a/lib/galaxy/datatypes/metadata.py b/lib/galaxy/datatypes/metadata.py index 6376531d9da..07363243d83 100644 --- a/lib/galaxy/datatypes/metadata.py +++ b/lib/galaxy/datatypes/metadata.py @@ -509,7 +509,7 @@ class JobExternalOutputMetadataWrapper( object ): # need to make different keys for them, since ids can overlap return "%s_%d" % ( dataset.__class__.__name__, dataset.id ) def setup_external_metadata( self, datasets, sa_session, exec_dir=None, tmp_dir=None, dataset_files_path=None, - output_fnames=None, config_root=None, datatypes_config=None, kwds={} ): + output_fnames=None, config_root=None, datatypes_config=None, job_metadata=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 ): def __get_filename_override(): @@ -564,7 +564,7 @@ class JobExternalOutputMetadataWrapper( object ): sa_session.flush() metadata_files_list.append( metadata_files ) #return command required to build - return "%s %s %s %s %s %s" % ( os.path.join( exec_dir, 'set_metadata.sh' ), dataset_files_path, tmp_dir, config_root, datatypes_config, " ".join( map( __metadata_files_list_to_cmd_line, metadata_files_list ) ) ) + return "%s %s %s %s %s %s %s" % ( os.path.join( exec_dir, 'set_metadata.sh' ), dataset_files_path, tmp_dir, config_root, datatypes_config, job_metadata, " ".join( map( __metadata_files_list_to_cmd_line, metadata_files_list ) ) ) def external_metadata_set_successfully( self, dataset, sa_session ): metadata_files = self.get_output_filenames_by_dataset( dataset, sa_session ) diff --git a/lib/galaxy/datatypes/registry.py b/lib/galaxy/datatypes/registry.py index 76d8939f7ff..a49444e6cae 100644 --- a/lib/galaxy/datatypes/registry.py +++ b/lib/galaxy/datatypes/registry.py @@ -3,7 +3,7 @@ Provides mapping between extensions and datatypes, mime-types, etc. """ import os, tempfile import logging -import data, tabular, interval, images, sequence, qualityscore, genetics, xml, coverage, tracks, chrominfo +import data, tabular, interval, images, sequence, qualityscore, genetics, xml, coverage, tracks, chrominfo, binary import galaxy.util from galaxy.util.odict import odict @@ -109,11 +109,11 @@ class Registry( object ): #default values if len(self.datatypes_by_extension) < 1: self.datatypes_by_extension = { - 'ab1' : images.Ab1(), + 'ab1' : binary.Ab1(), 'axt' : sequence.Axt(), - 'bam' : images.Bam(), + 'bam' : binary.Bam(), 'bed' : interval.Bed(), - 'binseq.zip' : images.Binseq(), + 'binseq.zip' : binary.Binseq(), 'blastxml' : xml.BlastXml(), 'coverage' : coverage.LastzCoverage(), 'customtrack' : interval.CustomTrack(), @@ -132,12 +132,12 @@ class Registry( object ): 'qualsolexa' : qualityscore.QualityScoreSolexa(), 'qual454' : qualityscore.QualityScore454(), 'sam' : tabular.Sam(), - 'scf' : images.Scf(), - 'sff' : data.Sff(), + 'scf' : binary.Scf(), + 'sff' : binary.Sff(), 'tabular' : tabular.Tabular(), 'taxonomy' : tabular.Taxonomy(), 'txt' : data.Text(), - 'txtseq.zip' : images.Txtseq(), + 'txtseq.zip' : data.Txtseq(), 'wig' : interval.Wiggle() } self.mimetypes_by_extension = { @@ -174,7 +174,7 @@ class Registry( object ): # because some formats are much more flexibly defined than others. if len(self.sniff_order) < 1: self.sniff_order = [ - data.Sff(), + binary.Sff(), xml.BlastXml(), sequence.Maf(), sequence.Lav(), diff --git a/lib/galaxy/datatypes/tracks.py b/lib/galaxy/datatypes/tracks.py index ed5b534aba6..2c4938c06ac 100644 --- a/lib/galaxy/datatypes/tracks.py +++ b/lib/galaxy/datatypes/tracks.py @@ -3,10 +3,7 @@ Datatype classes for tracks/track views within galaxy. """ import data -import logging -import re -import binascii -from cgi import escape +import tabular, binascii, logging from galaxy.datatypes.metadata import MetadataElement from galaxy.datatypes import metadata import galaxy.model @@ -17,7 +14,7 @@ from galaxy.util.hash_util import * log = logging.getLogger(__name__) -class GeneTrack( data.Binary ): +class GeneTrack( tabular.Tabular ): file_ext = "genetrack" MetadataElement( name="genetrack", default="data.genetrack", desc="HDF index", readonly=True, visible=True, no_value=0 ) @@ -27,7 +24,7 @@ class GeneTrack( data.Binary ): super( GeneTrack, self ).__init__( **kwargs ) self.add_display_app( 'genetrack', 'View in', '', 'genetrack_link' ) def get_display_links( self, dataset, type, app, base_url, target_frame='galaxy_main', **kwd ): - return data.Binary.get_display_links( self, dataset, type, app, base_url, target_frame=target_frame, **kwd ) + return data.Data.get_display_links( self, dataset, type, app, base_url, target_frame=target_frame, **kwd ) def genetrack_link( self, hda, type, app, base_url ): ret_val = [] if hda.has_data: diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index 73fa0096612..ce2197272b4 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -139,10 +139,15 @@ class JobQueue( object ): JobWrapper( job, None, self ).fail( 'This tool was disabled before the job completed. Please contact your Galaxy administrator, or' ) elif job.job_runner_name is None: log.debug( "no runner: %s is still in queued state, adding to the jobs queue" %job.id ) - self.queue.put( ( job.id, job.tool_id ) ) + if self.track_jobs_in_database: + job.state = model.Job.states.NEW + else: + self.queue.put( ( job.id, job.tool_id ) ) else: job_wrapper = JobWrapper( job, self.app.toolbox.tools_by_id[ job.tool_id ], self ) self.dispatcher.recover( job, job_wrapper ) + if self.sa_session.dirty: + self.sa_session.flush() def __monitor( self ): """ @@ -526,6 +531,7 @@ class JobWrapper( object ): # If the tool was expected to set the extension, attempt to retrieve it if dataset.ext == 'auto': dataset.extension = context.get( 'ext', 'data' ) + dataset.init_meta( copy_from=dataset ) #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, @@ -715,14 +721,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, config_root = None, datatypes_config = None, **kwds ): + def setup_external_metadata( self, exec_dir = None, tmp_dir = None, dataset_files_path = None, config_root = None, datatypes_config = None, set_extension = True, **kwds ): # extension could still be 'auto' if this is the upload tool. job = self.sa_session.query( model.Job ).get( self.job_id ) - for output_dataset_assoc in job.output_datasets: - if output_dataset_assoc.dataset.ext == 'auto': - context = self.get_dataset_finish_context( dict(), output_dataset_assoc.dataset.dataset ) - output_dataset_assoc.dataset.extension = context.get( 'ext', 'data' ) - self.sa_session.flush() + if set_extension: + for output_dataset_assoc in job.output_datasets: + if output_dataset_assoc.dataset.ext == 'auto': + context = self.get_dataset_finish_context( dict(), output_dataset_assoc.dataset.dataset ) + output_dataset_assoc.dataset.extension = context.get( 'ext', 'data' ) + self.sa_session.flush() if tmp_dir is None: #this dir should should relative to the exec_dir tmp_dir = self.app.config.new_file_path @@ -739,6 +746,7 @@ class JobWrapper( object ): dataset_files_path = dataset_files_path, config_root = config_root, datatypes_config = datatypes_config, + job_metadata = os.path.join( self.working_directory, TOOL_PROVIDED_JOB_METADATA_FILE ), **kwds ) class DefaultJobDispatcher( object ): diff --git a/lib/galaxy/jobs/runners/local.py b/lib/galaxy/jobs/runners/local.py index e9de560bb10..a2f10cbc1c3 100644 --- a/lib/galaxy/jobs/runners/local.py +++ b/lib/galaxy/jobs/runners/local.py @@ -106,7 +106,9 @@ class LocalJobRunner( object ): #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 and job_wrapper.output_paths: - external_metadata_script = job_wrapper.setup_external_metadata( output_fnames = job_wrapper.get_output_fnames(), kwds = { 'overwrite' : False } ) #we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior + external_metadata_script = job_wrapper.setup_external_metadata( output_fnames = job_wrapper.get_output_fnames(), + set_extension = True, + 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, diff --git a/lib/galaxy/jobs/runners/pbs.py b/lib/galaxy/jobs/runners/pbs.py index 835424d7fe9..921b1a605d8 100644 --- a/lib/galaxy/jobs/runners/pbs.py +++ b/lib/galaxy/jobs/runners/pbs.py @@ -29,7 +29,6 @@ if [ "$GALAXY_LIB" != "None" ]; then fi cd %s %s -%s """ pbs_symlink_template = """#!/bin/sh @@ -178,7 +177,9 @@ class PBSJobRunner( object ): pbs_queue_name = self.determine_pbs_queue( runner_url ) c = pbs.pbs_connect( pbs_server_name ) if c <= 0: - raise Exception( "Connection to PBS server for submit failed" ) + job_wrapper.fail( "Unable to queue job for execution. Resubmitting the job may succeed." ) + log.error( "Connection to PBS server for submit failed" ) + return # define job attributes ofile = "%s/%s.o" % (self.app.config.cluster_files_directory, job_wrapper.job_id) @@ -221,11 +222,15 @@ 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() + output_files), 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 = os.path.abspath( os.getcwd() ), tmp_dir = self.app.config.new_file_path, dataset_files_path = self.app.model.Dataset.file_path, output_fnames = output_fnames, 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 ) + script += "cd %s\n" % os.path.abspath( os.getcwd() ) + script += "%s\n" % job_wrapper.setup_external_metadata( exec_dir = os.path.abspath( os.getcwd() ), + tmp_dir = self.app.config.new_file_path, + dataset_files_path = self.app.model.Dataset.file_path, + output_fnames = output_fnames, + set_extension = False, + kwds = { 'overwrite' : False } ) #we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior 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/tools/actions/metadata.py b/lib/galaxy/tools/actions/metadata.py index 7cbb6a1705a..420812bac56 100644 --- a/lib/galaxy/tools/actions/metadata.py +++ b/lib/galaxy/tools/actions/metadata.py @@ -41,6 +41,7 @@ class SetMetadataToolAction( ToolAction ): output_fnames = None, config_root = None, datatypes_config = None, + job_metadata = None, kwds = { 'overwrite' : True } ) incoming[ '__SET_EXTERNAL_METADATA_COMMAND_LINE__' ] = cmd_line for name, value in tool.params_to_strings( incoming, trans.app ).iteritems(): diff --git a/lib/galaxy/web/controllers/dataset.py b/lib/galaxy/web/controllers/dataset.py index 762673265ea..6fde6a8e158 100644 --- a/lib/galaxy/web/controllers/dataset.py +++ b/lib/galaxy/web/controllers/dataset.py @@ -20,25 +20,38 @@ GALAXY TOOL ERROR REPORT This error report was sent from the Galaxy instance hosted on the server "${host}" ----------------------------------------------------------------------------- -This is in reference to output dataset ${dataset_id}. +This is in reference to dataset id ${dataset_id} from history id ${history_id} +----------------------------------------------------------------------------- +You should be able to view the history containing the related history item + +${hid}: ${history_item_name} + +by logging in as a Galaxy admin user to the Galaxy instance referenced above +and pointing your browser to the following link. + +${history_view_link} ----------------------------------------------------------------------------- The user '${email}' provided the following information: + ${message} ----------------------------------------------------------------------------- job id: ${job_id} -tool id: ${tool_id} +tool id: ${job_tool_id} +----------------------------------------------------------------------------- +job command line: +${job_command_line} ----------------------------------------------------------------------------- job stderr: -${stderr} +${job_stderr} ----------------------------------------------------------------------------- job stdout: -${stdout} +${job_stdout} ----------------------------------------------------------------------------- job info: -${info} +${job_info} ----------------------------------------------------------------------------- job traceback: -${traceback} +${job_traceback} ----------------------------------------------------------------------------- (This is an automated message). """ @@ -103,41 +116,45 @@ class DatasetInterface( BaseController ): @web.expose def errors( self, trans, id ): - dataset = trans.sa_session.query( model.HistoryDatasetAssociation ).get( id ) - return trans.fill_template( "dataset/errors.mako", dataset=dataset ) - + hda = trans.sa_session.query( model.HistoryDatasetAssociation ).get( id ) + return trans.fill_template( "dataset/errors.mako", hda=hda ) @web.expose def stderr( self, trans, id ): dataset = trans.sa_session.query( model.HistoryDatasetAssociation ).get( id ) job = dataset.creating_job_associations[0].job trans.response.set_content_type( 'text/plain' ) return job.stderr - @web.expose def report_error( self, trans, id, email='', message="" ): smtp_server = trans.app.config.smtp_server if smtp_server is None: - return trans.show_error_message( "Sorry, mail is not configured for this galaxy instance" ) + return trans.show_error_message( "Mail is not configured for this galaxy instance" ) to_address = trans.app.config.error_email_to if to_address is None: - return trans.show_error_message( "Sorry, error reporting has been disabled for this galaxy instance" ) + return trans.show_error_message( "Error reporting has been disabled for this galaxy instance" ) # Get the dataset and associated job - dataset = trans.sa_session.query( model.HistoryDatasetAssociation ).get( id ) - job = dataset.creating_job_associations[0].job + hda = trans.sa_session.query( model.HistoryDatasetAssociation ).get( id ) + job = hda.creating_job_associations[0].job # Get the name of the server hosting the Galaxy instance from which this report originated host = trans.request.host + history_view_link = "%s/history/view?id=%s" % ( str( host ), trans.security.encode_id( hda.history_id ) ) # Build the email message msg = MIMEText( string.Template( error_report_template ) .safe_substitute( host=host, - dataset_id=dataset.id, - email=email, - message=message, + dataset_id=hda.dataset_id, + history_id=hda.history_id, + hid=hda.hid, + history_item_name=hda.get_display_name(), + history_view_link=history_view_link, job_id=job.id, - tool_id=job.tool_id, - stderr=job.stderr, - stdout=job.stdout, - traceback=job.traceback, - info=job.info ) ) + job_tool_id=job.tool_id, + job_command_line=job.command_line, + job_stderr=job.stderr, + job_stdout=job.stdout, + job_info=job.info, + job_traceback=job.traceback, + email=email, + message=message ) ) frm = to_address # Check email a bit email = email.strip() diff --git a/lib/galaxy/web/controllers/forms.py b/lib/galaxy/web/controllers/forms.py index 96eb1d8a9c9..b6485339d53 100644 --- a/lib/galaxy/web/controllers/forms.py +++ b/lib/galaxy/web/controllers/forms.py @@ -8,9 +8,71 @@ from galaxy.tools.parameters.basic import parameter_types from elementtree.ElementTree import XML, Element from galaxy.util.odict import odict import copy +from galaxy.web.framework.helpers import time_ago, iff, grids log = logging.getLogger( __name__ ) +class FormsGrid( grids.Grid ): + # Custom column types + class NameColumn( grids.TextColumn ): + def get_value(self, trans, grid, form): + return form.latest_form.name + class DescriptionColumn( grids.TextColumn ): + def get_value(self, trans, grid, form): + return form.latest_form.desc + class TypeColumn( grids.TextColumn ): + def get_value(self, trans, grid, form): + return form.latest_form.type + class DeletedColumn( grids.GridColumn ): + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + accepted_filter_labels_and_vals = { "active" : "False", "deleted" : "True", "all": "All" } + accepted_filters = [] + for label, val in accepted_filter_labels_and_vals.items(): + args = { self.key: val } + accepted_filters.append( grids.GridColumnFilter( label, args) ) + return accepted_filters + # Grid definition + title = "Forms" + template = "admin/forms/grid.mako" + model_class = model.FormDefinitionCurrent + default_sort_key = "-create_time" + num_rows_per_page = 50 + preserve_state = True + use_paging = True + default_filter = dict( deleted="False" ) + columns = [ + NameColumn( "Name", + key="name", + model_class=model.FormDefinition, + link=( lambda item: iff( item.deleted, None, dict( operation="view", id=item.id ) ) ), + attach_popup=True, + filterable="advanced" ), + DescriptionColumn( "Description", + key='desc', + model_class=model.FormDefinition, + filterable="advanced" ), + TypeColumn( "Type" ), + DeletedColumn( "Deleted", + key="deleted", + visible=False, + filterable="advanced" ) + ] + columns.append( grids.MulticolFilterColumn( "Search", + cols_to_filter=[ columns[0], columns[1] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + operations = [ + grids.GridOperation( "Edit", allow_multiple=False, condition=( lambda item: not item.deleted ) ), + grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted ) ), + grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ), + ] + global_actions = [ + grids.GridAction( "Create new form", dict( controller='forms', + action='new' ) ) + ] + class Forms( BaseController ): # Empty form field empty_field = { 'label': '', @@ -20,38 +82,38 @@ class Forms( BaseController ): 'type': BaseField.form_field_types()[0], 'selectlist': [], 'layout': 'none' } + forms_grid = FormsGrid() + @web.expose @web.require_admin - 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( "/sample/index.mako", - default_action=params.get( 'default_action', None ), - msg=msg, - messagetype=messagetype ) - @web.expose - @web.require_admin - def manage( self, trans, **kwd ): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - show_filter = params.get( 'show_filter', 'Active' ) - return self._show_forms_list(trans, msg, messagetype, show_filter) - def _show_forms_list(self, trans, msg, messagetype, show_filter='Active'): - all_forms = trans.sa_session.query( trans.app.model.FormDefinitionCurrent ) - if show_filter == 'All': - forms_list = all_forms - elif show_filter == 'Deleted': - forms_list = [form for form in all_forms if form.deleted] - else: - forms_list = [form for form in all_forms if not form.deleted] - return trans.fill_template( '/admin/forms/manage_forms.mako', - fdc_list=forms_list, - all_forms=all_forms, - show_filter=show_filter, - msg=msg, - messagetype=messagetype ) + def manage( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() + if not kwd.get( 'id', None ): + return trans.response.send_redirect( web.url_for( controller='forms', + action='manage', + status='error', + message="Invalid form ID") ) + if operation == "view": + return self.__view( trans, **kwd ) + elif operation == "delete": + return self.__delete( trans, **kwd ) + elif operation == "undelete": + return self.__undelete( trans, **kwd ) + elif operation == "edit": + return self.__edit( trans, **kwd ) + return self.forms_grid( trans, **kwd ) + def __view(self, trans, **kwd): + try: + fdc = trans.sa_session.query( trans.app.model.FormDefinitionCurrent )\ + .get( trans.security.decode_id(kwd['id']) ) + except: + return trans.response.send_redirect( web.url_for( controller='forms', + action='manage', + msg='Invalid form', + messagetype='error' ) ) + return trans.fill_template( '/admin/forms/show_form_read_only.mako', + form=fdc.latest_form ) def __form_types_widget(self, trans, selected='none'): form_type_selectbox = SelectField( 'form_type_selectbox', refresh_on_change=True, @@ -86,13 +148,14 @@ class Forms( BaseController ): self.__get_saved_form( fd ) if self.__imported_from_file: return trans.response.send_redirect( web.url_for( controller='forms', - action='edit', - show_form=True, - form_id=fd.id) ) + action='manage', + operation='edit', + id=trans.security.encode_id(fd.current.id)) ) else: return trans.response.send_redirect( web.url_for( controller='forms', - action='edit', - form_id=fd.id, + action='manage', + operation='edit', + id=trans.security.encode_id(fd.current.id), add_field_button='Add field', name=fd.name, description=fd.desc, @@ -105,35 +168,43 @@ class Forms( BaseController ): inputs=inputs, msg=msg, messagetype=messagetype ) - @web.expose - @web.require_admin - def delete( self, trans, **kwd ): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - fd = trans.sa_session.query( trans.app.model.FormDefinition ).get( int( util.restore_text( params.form_id ) ) ) - fd.form_definition_current.deleted = True - trans.sa_session.add( fd.form_definition_current ) - trans.sa_session.flush() - return self._show_forms_list(trans, - msg='The form definition named %s is deleted.' % fd.name, - messagetype='done') - @web.expose - @web.require_admin - def undelete( self, trans, **kwd ): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - fd = trans.sa_session.query( trans.app.model.FormDefinition ).get( int( util.restore_text( params.form_id ) ) ) - fd.form_definition_current.deleted = False - trans.sa_session.add( fd.form_definition_current ) - trans.sa_session.flush() - return self._show_forms_list(trans, - msg='The form definition named %s is undeleted.' % fd.name, - messagetype='done') - @web.expose - @web.require_admin - def edit( self, trans, **kwd ): + def __delete( self, trans, **kwd ): + id_list = util.listify( kwd['id'] ) + delete_failed = [] + for id in id_list: + try: + fdc = trans.sa_session.query( trans.app.model.FormDefinitionCurrent ).get( trans.security.decode_id(id) ) + except: + return trans.response.send_redirect( web.url_for( controller='forms', + action='manage', + message='Invalid form', + status='error' ) ) + fdc.deleted = True + trans.sa_session.add( fdc ) + trans.sa_session.flush() + return trans.response.send_redirect( web.url_for( controller='forms', + action='manage', + message='%i form(s) is deleted.' % len(id_list), + status='done') ) + def __undelete( self, trans, **kwd ): + id_list = util.listify( kwd['id'] ) + delete_failed = [] + for id in id_list: + try: + fdc = trans.sa_session.query( trans.app.model.FormDefinitionCurrent ).get( trans.security.decode_id(id) ) + except: + return trans.response.send_redirect( web.url_for( controller='forms', + action='manage', + message='Invalid form', + status='error' ) ) + fdc.deleted = False + trans.sa_session.add( fdc ) + trans.sa_session.flush() + return trans.response.send_redirect( web.url_for( controller='forms', + action='manage', + message='%i form(s) is undeleted.' % len(id_list), + status='done') ) + def __edit( self, trans, **kwd ): ''' This callback method is for handling all the editing functions like renaming fields, adding/deleting fields, changing fields attributes. @@ -142,17 +213,28 @@ class Forms( BaseController ): msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) try: - fd = trans.sa_session.query( trans.app.model.FormDefinition ).get( int( params.get( 'form_id', None ) ) ) + fdc = trans.sa_session.query( trans.app.model.FormDefinitionCurrent ).get( trans.security.decode_id(kwd['id']) ) except: return trans.response.send_redirect( web.url_for( controller='forms', action='manage', - msg='Invalid form', - messagetype='error' ) ) + message='Invalid form', + status='error' ) ) + fd = fdc.latest_form # - # Show the form for editing + # Save changes # - if params.get( 'show_form', False ): + if params.get( 'save_changes_button', False ): + fd_new, msg = self.__save_form( trans, fdc_id=fd.form_definition_current.id, **kwd ) + # if validation error encountered while saving the form, show the + # unsaved form, with the error message + if not fd_new: + current_form = self.__get_form( trans, **kwd ) + return self.__show( trans=trans, form=fd, current_form=current_form, + msg=msg, messagetype='error', **kwd ) + # everything went fine. form saved successfully. Show the saved form + fd = fd_new current_form = self.__get_saved_form( fd ) + msg = "The form '%s' has been updated with the changes." % fd.name return self.__show( trans=trans, form=fd, current_form=current_form, msg=msg, messagetype=messagetype, **kwd ) # @@ -193,31 +275,6 @@ class Forms( BaseController ): return self.__show( trans=trans, form=fd, current_form=current_form, msg=msg, messagetype=messagetype, **kwd ) # - # Save changes - # - elif params.get( 'save_changes_button', False ): - fd_new, msg = self.__save_form( trans, fdc_id=fd.form_definition_current.id, **kwd ) - # if validation error encountered while saving the form, show the - # unsaved form, with the error message - if not fd_new: - current_form = self.__get_form( trans, **kwd ) - return self.__show( trans=trans, form=fd, current_form=current_form, - msg=msg, messagetype='error', **kwd ) - # everything went fine. form saved successfully. Show the saved form - fd = fd_new - current_form = self.__get_saved_form( fd ) - msg = "The form '%s' has been updated with the changes." % fd.name - return self.__show( trans=trans, form=fd, current_form=current_form, - msg=msg, messagetype=messagetype, **kwd ) - # - # Show form read-only - # - elif params.get( 'read_only', False ): - return trans.fill_template( '/admin/forms/show_form_read_only.mako', - form=fd, - msg=msg, - messagetype=messagetype ) - # # Add SelectField option # elif 'Add' in kwd.values(): @@ -234,6 +291,13 @@ class Forms( BaseController ): current_form = self.__get_form( trans, **kwd ) return self.__show( trans=trans, form=fd, current_form=current_form, msg=msg, messagetype=messagetype, **kwd ) + # + # Show the form for editing + # + else: + current_form = self.__get_saved_form( fd ) + return self.__show( trans=trans, form=fd, current_form=current_form, + msg=msg, messagetype=messagetype, **kwd ) def __add_selectbox_option( self, trans, fd, msg, messagetype, **kwd ): ''' diff --git a/lib/galaxy/web/controllers/history.py b/lib/galaxy/web/controllers/history.py index eca58b94811..5fca0008392 100644 --- a/lib/galaxy/web/controllers/history.py +++ b/lib/galaxy/web/controllers/history.py @@ -87,7 +87,7 @@ class HistoryListGrid( grids.Grid ): # Grid definition title = "Saved Histories" model_class = model.History - template='/history/grid.mako' + template='/grid_base.mako' default_sort_key = "-create_time" columns = [ NameColumn( "Name", key="name", model_class=model.History, @@ -110,14 +110,14 @@ class HistoryListGrid( grids.Grid ): ) operations = [ - grids.GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ) ), - grids.GridOperation( "Share", condition=( lambda item: not item.deleted ) ), - grids.GridOperation( "Unshare", condition=( lambda item: not item.deleted ) ), - grids.GridOperation( "Rename", condition=( lambda item: not item.deleted ) ), - grids.GridOperation( "Delete", condition=( lambda item: not item.deleted ) ), - grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ), - grids.GridOperation( "Enable import via link", condition=( lambda item: item.deleted ) ), - grids.GridOperation( "Disable import via link", condition=( lambda item: item.deleted ) ) + grids.GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=True ), + grids.GridOperation( "Share", condition=( lambda item: not item.deleted ), async_compatible=False ), + grids.GridOperation( "Unshare", condition=( lambda item: not item.deleted ), async_compatible=False ), + grids.GridOperation( "Rename", condition=( lambda item: not item.deleted ), async_compatible=False ), + grids.GridOperation( "Delete", condition=( lambda item: not item.deleted ), async_compatible=True ), + grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ), async_compatible=True ), + grids.GridOperation( "Enable import via link", condition=( lambda item: item.deleted ), async_compatible=True ), + grids.GridOperation( "Disable import via link", condition=( lambda item: item.deleted ), async_compatible=True ) ] standard_filters = [ grids.GridColumnFilter( "Active", args=dict( deleted=False ) ), @@ -262,7 +262,7 @@ class HistoryController( BaseController ): n_deleted += 1 status = SUCCESS if n_deleted: - message_parts.append( "Deleted %d histories. " % n_deleted ) + message_parts.append( "Deleted %d %s. " % ( n_deleted, iff( n_deleted != 1, "histories", "history" ) ) ) if deleted_current: message_parts.append( "Your active history was deleted, a new empty history is now active. " ) status = INFO @@ -290,7 +290,7 @@ class HistoryController( BaseController ): status = SUCCESS message_parts = [] if n_undeleted: - message_parts.append( "Undeleted %d histories." % n_undeleted ) + message_parts.append( "Undeleted %d %s. " % ( n_undeleted, iff( n_undeleted != 1, "histories", "history" ) ) ) if n_already_purged: message_parts.append( "%d histories have already been purged and cannot be undeleted." % n_already_purged ) status = WARNING @@ -438,23 +438,20 @@ class HistoryController( BaseController ): Warning! If you import this history, you will lose your current history. Click here to confirm. """ % web.url_for( id=id, confirm=True ) ) - @web.expose def view( self, trans, id=None ): """View a history. If a history is importable, then it is viewable by any user.""" - # Get history to view. if not id: return trans.show_error_message( "You must specify a history you want to view." ) history_to_view = get_history( trans, id, False) - # Integrity checks. if not history_to_view: - return trans.show_error_message( "The specified history does not exist.") + return trans.show_error_message( "The specified history does not exist." ) + # Admin users can view any history # TODO: Use a new flag to determine if history is viewable? - if not history_to_view.importable: - error( "The owner of this history has not published this history." ) - + if not trans.user_is_admin and not history_to_view.importable: + error( "Either you are not allowed to view this history or the owner of this history has not published it." ) # View history. query = trans.sa_session.query( model.HistoryDatasetAssociation ) \ .filter( model.HistoryDatasetAssociation.history == history_to_view ) \ @@ -469,7 +466,6 @@ class HistoryController( BaseController ): datasets = query.all(), user_owns_history = user_owns_history, show_deleted = False ) - @web.expose @web.require_login( "share histories with other users" ) def share( self, trans, id=None, email="", **kwd ): diff --git a/lib/galaxy/web/controllers/library_common.py b/lib/galaxy/web/controllers/library_common.py index d3ac8e0b588..60222457272 100644 --- a/lib/galaxy/web/controllers/library_common.py +++ b/lib/galaxy/web/controllers/library_common.py @@ -117,7 +117,7 @@ class LibraryCommon( BaseController ): uploaded_dataset.link_data_only = True uploaded_dataset.data.file_name = os.path.abspath( path ) trans.sa_session.add( uploaded_dataset.data ) - trans.sa_session.data.flush() + trans.sa_session.flush() return uploaded_dataset def get_server_dir_uploaded_datasets( self, trans, params, full_dir, import_dir_desc, library_bunch, err_redirect, msg ): files = [] diff --git a/lib/galaxy/web/controllers/page.py b/lib/galaxy/web/controllers/page.py index 0923cef5ee8..5f8c0f63bc5 100644 --- a/lib/galaxy/web/controllers/page.py +++ b/lib/galaxy/web/controllers/page.py @@ -1,6 +1,7 @@ from galaxy.web.base.controller import * from galaxy.web.framework.helpers import time_ago, grids from galaxy.util.sanitize_html import sanitize_html +from galaxy.util.odict import odict import re @@ -69,21 +70,66 @@ class PageAllPublishedGrid( grids.Grid ): ] def apply_default_filter( self, trans, query, **kwargs ): return query.filter_by( deleted=False, published=True ) - - -class NameColumn( grids.TextColumn ): - def get_value(self, trans, grid, history): - return history.get_display_name() class HistorySelectionGrid( grids.Grid ): + # Custom columns. + class NameColumn( grids.TextColumn ): + def get_value(self, trans, grid, history): + return history.get_display_name() + + class DeletedColumn( grids.GridColumn ): + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + accepted_filter_labels_and_vals = { "active" : "False", "deleted" : "True", "all": "All" } + accepted_filters = [] + for label, val in accepted_filter_labels_and_vals.items(): + args = { self.key: val } + accepted_filters.append( grids.GridColumnFilter( label, args) ) + return accepted_filters + + class SharingColumn( grids.GridColumn ): + def filter( self, db_session, query, column_filter ): + """ Modify query to filter histories by sharing status. """ + if column_filter == "All": + pass + elif column_filter: + if column_filter == "private": + query = query.filter( model.History.users_shared_with == None ) + query = query.filter( model.History.importable == False ) + elif column_filter == "shared": + query = query.filter( model.History.users_shared_with != None ) + elif column_filter == "importable": + query = query.filter( model.History.importable == True ) + return query + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + accepted_filter_labels_and_vals = odict() + accepted_filter_labels_and_vals["private"] = "private" + accepted_filter_labels_and_vals["shared"] = "shared" + accepted_filter_labels_and_vals["importable"] = "importable" + accepted_filter_labels_and_vals["all"] = "All" + accepted_filters = [] + for label, val in accepted_filter_labels_and_vals.items(): + args = { self.key: val } + accepted_filters.append( grids.GridColumnFilter( label, args) ) + return accepted_filters + # Grid definition. title = "Saved Histories" + template = "grid_base_async.mako" + async_template = "grid_body_async.mako" model_class = model.History + default_filter = { "deleted" : "False" , "shared" : "All" } default_sort_key = "-update_time" + use_paging = True + num_rows_per_page = 5 columns = [ NameColumn( "Name", key="name", model_class=model.History, filterable="advanced" ), grids.TagsColumn( "Tags", "tags", model.History, model.HistoryTagAssociation, filterable="advanced"), grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), + # Columns that are valid for filtering but are not visible. + DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ), + SharingColumn( "Shared", key="shared", visible=False, filterable="advanced" ), ] columns.append( grids.MulticolFilterColumn( @@ -91,6 +137,8 @@ class HistorySelectionGrid( grids.Grid ): cols_to_filter=[ columns[0], columns[1] ], key="free-text-search", visible=False, filterable="standard" ) ) + def apply_default_filter( self, trans, query, **kwargs ): + return query.filter_by( user=trans.user, purged=False ) class PageController( BaseController ): @@ -268,4 +316,4 @@ class PageController( BaseController ): @web.require_login("select a history from saved histories") def list_histories_for_selection( self, trans, **kwargs ): # Render the list view - return self._history_selection_grid( trans, status=status, message=message, **kwargs ) \ No newline at end of file + return self._history_selection_grid( trans, **kwargs ) \ No newline at end of file diff --git a/lib/galaxy/web/controllers/requests.py b/lib/galaxy/web/controllers/requests.py index 2b938a6a362..75afe8ae6df 100644 --- a/lib/galaxy/web/controllers/requests.py +++ b/lib/galaxy/web/controllers/requests.py @@ -12,57 +12,109 @@ from cgi import escape, FieldStorage log = logging.getLogger( __name__ ) -class RequestsListGrid( grids.Grid ): +class RequestsGrid( grids.Grid ): + # Custom column types + class NameColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.name + class DescriptionColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.desc + class SamplesColumn( grids.GridColumn ): + def get_value(self, trans, grid, request): + return str(len(request.samples)) + class TypeColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.type.name + class LastUpdateColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.update_time + class StateColumn( grids.GridColumn ): + def filter( self, db_session, query, column_filter ): + """ Modify query to filter request by state. """ + if column_filter == "All": + return query + if column_filter: + query = query.filter( model.Request.state == column_filter ) + return query + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + accepted_filter_labels_and_vals = [ model.Request.states.UNSUBMITTED, + model.Request.states.SUBMITTED, + model.Request.states.COMPLETE, + "All"] + accepted_filters = [] + for val in accepted_filter_labels_and_vals: + label = val.lower() + args = { self.key: val } + accepted_filters.append( grids.GridColumnFilter( label, args) ) + return accepted_filters + class DeletedColumn( grids.GridColumn ): + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + accepted_filter_labels_and_vals = { "active" : "False", "deleted" : "True", "all": "All" } + accepted_filters = [] + for label, val in accepted_filter_labels_and_vals.items(): + args = { self.key: val } + accepted_filters.append( grids.GridColumnFilter( label, args) ) + return accepted_filters + # Grid definition title = "Sequencing Requests" - template = '/requests/grid.mako' + template = 'requests/grid.mako' model_class = model.Request default_sort_key = "-create_time" - show_filter = model.Request.states.UNSUBMITTED + num_rows_per_page = 50 + preserve_state = True + use_paging = True + default_filter = dict( deleted="False", state=model.Request.states.UNSUBMITTED) columns = [ - grids.GridColumn( "Name", key="name", - link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), - attach_popup=True ), - grids.GridColumn( "Description", key='desc'), - grids.GridColumn( "Sample(s)", method='number_of_samples', - link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), ), - grids.GridColumn( "Type", key="request_type_id", method='get_request_type'), - grids.GridColumn( "Last update", key="update_time", format=time_ago ), - grids.GridColumn( "State", key='state'), + NameColumn( "Name", + key="name", + model_class=model.Request, + link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), + attach_popup=True, + filterable="advanced" ), + DescriptionColumn( "Description", + key='desc', + model_class=model.Request, + filterable="advanced" ), + SamplesColumn( "Sample(s)", + link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), ), + TypeColumn( "Type" ), + LastUpdateColumn( "Last update", + format=time_ago ), + StateColumn( "State", + key='state', + filterable="advanced"), + DeletedColumn( "Deleted", + key="deleted", + visible=True, + filterable="advanced" ) ] + columns.append( grids.MulticolFilterColumn( "Search", + cols_to_filter=[ columns[0], columns[1] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) operations = [ grids.GridOperation( "Submit", allow_multiple=False, condition=( lambda item: not item.deleted and item.unsubmitted() and item.samples ) ), grids.GridOperation( "Edit", allow_multiple=False, condition=( lambda item: not item.deleted and item.unsubmitted() ) ), - grids.GridOperation( "Delete", allow_multiple=False, condition=( lambda item: not item.deleted and item.unsubmitted() ) ), - grids.GridOperation( "Undelete", allow_multiple=False, condition=( lambda item: item.deleted ) ) + grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted and item.unsubmitted() ) ), + grids.GridOperation( "Undelete", allow_multiple=True, condition=( lambda item: item.deleted ) ) ] - standard_filters = [ - grids.GridColumnFilter( model.Request.states.UNSUBMITTED, - args=dict( state=model.Request.states.UNSUBMITTED, deleted=False ) ), - grids.GridColumnFilter( model.Request.states.SUBMITTED, - args=dict( state=model.Request.states.SUBMITTED, deleted=False ) ), - grids.GridColumnFilter( model.Request.states.COMPLETE, args=dict( state=model.Request.states.COMPLETE, deleted=False ) ), - grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), - grids.GridColumnFilter( "All", args={} ) + global_actions = [ + grids.GridAction( "Create new request", dict( controller='requests', + action='new', + select_request_type='True' ) ) ] - #default_filter = dict( deleted=False ) - def get_current_item( self, trans ): - return None - def get_request_type(self, trans, request): - return request.type.name - def apply_default_filter( self, trans, query, **kwargs ): - query = query.filter_by( user=trans.user ) - if self.default_filter: - return query.filter_by( **self.default_filter ) - else: - return query - def number_of_samples(self, trans, request): - return str(len(request.samples)) - def get_state(self, trans, request): - return request.state + def apply_default_filter( self, trans, query, **kwd ): + return query.filter_by( user=trans.user ) + def build_initial_query( self, session ): + return session.query( self.model_class ) class Requests( BaseController ): - request_grid = RequestsListGrid() + request_grid = RequestsGrid() @web.expose @web.require_login( "create/submit sequencing requests" ) @@ -71,50 +123,43 @@ class Requests( BaseController ): @web.expose @web.require_login( "create/submit sequencing requests" ) - def list( self, trans, **kwargs ): + def list( self, trans, **kwd ): ''' List all request made by the current user ''' - status = message = None - self.request_grid.default_filter = dict(state=trans.app.model.Request.states.UNSUBMITTED, - deleted=False) - if 'operation' in kwargs: - operation = kwargs['operation'].lower() + + if 'operation' in kwd: + operation = kwd['operation'].lower() + if not kwd.get( 'id', None ): + return trans.response.send_redirect( web.url_for( controller='requests', + action='list', + status='error', + message="Invalid request ID") ) if operation == "show_request": - id = trans.security.decode_id(kwargs['id']) - return self.__show_request(trans, id, kwargs.get('add_sample', False)) + return self.__show_request( trans, **kwd ) elif operation == "submit": - id = trans.security.decode_id(kwargs['id']) - return self.__submit_request(trans, id) + return self.__submit_request( trans, **kwd ) elif operation == "delete": - id = trans.security.decode_id(kwargs['id']) - return self.__delete_request(trans, id) + return self.__delete_request( trans, **kwd ) elif operation == "undelete": - id = trans.security.decode_id(kwargs['id']) - return self.__undelete_request(trans, id) + return self.__undelete_request( trans, **kwd ) elif operation == "edit": - id = trans.security.decode_id(kwargs['id']) - return self.__edit_request(trans, id) - if 'show_filter' in kwargs.keys(): - if kwargs['show_filter'] == 'All': - self.request_grid.default_filter = {} - elif kwargs['show_filter'] == 'Deleted': - self.request_grid.default_filter = dict(deleted=True) - else: - self.request_grid.default_filter = dict(state=kwargs['show_filter'], deleted=False) - self.request_grid.show_filter = kwargs.get('show_filter', trans.app.model.Request.states.UNSUBMITTED) + return self.__edit_request( trans, **kwd ) # Render the list view - return self.request_grid( trans, **kwargs ) + return self.request_grid( trans, **kwd ) - def __show_request(self, trans, id, add_sample=False): + def __show_request(self, trans, **kwd): + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + add_sample = params.get('add_sample', False) try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) except: return trans.response.send_redirect( web.url_for( controller='requests', action='list', status='error', - message="Invalid request ID", - **kwd) ) + message="Invalid request ID" ) ) current_samples = [] for s in request.samples: current_samples.append([s.name, s.values.content]) @@ -122,10 +167,11 @@ class Requests( BaseController ): current_samples.append(['Sample_%i' % (len(current_samples)+1),['' for field in request.type.sample_form.fields]]) return trans.fill_template( '/requests/show_request.mako', request=request, - request_details=self.request_details(trans, id), + request_details=self.request_details(trans, request.id), current_samples = current_samples, sample_copy=self.__copy_sample(current_samples), - details='hide', edit_mode='False') + details='hide', edit_mode='False', + msg=msg, messagetype=messagetype ) def request_details(self, trans, id): ''' Shows the request details @@ -685,7 +731,7 @@ class Requests( BaseController ): message="Invalid request ID", **kwd) ) if params.get('show', False) == 'True': - return self.__edit_request(trans, request.id, **kwd) + return self.__edit_request(trans, **kwd) elif params.get('save_changes_request_button', False) == 'Save changes' \ or params.get('edit_samples_button', False) == 'Edit samples': request_type = trans.sa_session.query( trans.app.model.RequestType ).get( int( params.select_request_type ) ) @@ -714,11 +760,11 @@ class Requests( BaseController ): messagetype='done', **new_kwd) ) elif params.get('refresh', False) == 'true': - return self.__edit_request(trans, request.id, **kwd) + return self.__edit_request(trans, **kwd) - def __edit_request(self, trans, id, **kwd): + def __edit_request(self, trans, **kwd): try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) except: msg = "Invalid request ID" log.warn( msg ) @@ -758,59 +804,61 @@ class Requests( BaseController ): msg=msg, messagetype=messagetype) return self.__show_request_form(trans) - def __delete_request(self, trans, id): - try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) - except: - msg = "Invalid request ID" - log.warn( msg ) - return trans.response.send_redirect( web.url_for( controller='requests', - action='list', - status='error', - message=msg, - **kwd) ) - # change request's submitted field - if not request.unsubmitted(): - return trans.response.send_redirect( web.url_for( controller='requests', - action='list', - status='error', - message='This request cannot be deleted as it is already been submitted', - **kwd) ) - request.deleted = True - trans.sa_session.add( request ) - trans.sa_session.flush() - kwd = {} - kwd['id'] = trans.security.encode_id(request.id) + def __delete_request(self, trans, **kwd): + id_list = util.listify( kwd['id'] ) + delete_failed = [] + for id in id_list: + try: + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) ) + except: + msg = "Invalid request ID" + log.warn( msg ) + return trans.response.send_redirect( web.url_for( controller='requests', + action='list', + status='error', + message=msg, + **kwd) ) + # a request cannot be deleted once its submitted + if not request.unsubmitted(): + delete_failed.append(request.name) + else: + request.deleted = True + trans.sa_session.add( request ) + trans.sa_session.flush() + if not len(delete_failed): + msg = '%i request(s) has been deleted.' % len(id_list) + status = 'done' + else: + msg = '%i request(s) has been deleted. %i request %s could not be deleted as they have been submitted.' % (len(id_list)-len(delete_failed), + len(delete_failed), str(delete_failed)) + status = 'warning' + return trans.response.send_redirect( web.url_for( controller='requests', + action='list', + status=status, + message=msg) ) + def __undelete_request(self, trans, **kwd): + id_list = util.listify( kwd['id'] ) + for id in id_list: + try: + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) ) + except: + msg = "Invalid request ID" + log.warn( msg ) + return trans.response.send_redirect( web.url_for( controller='requests', + action='list', + status='error', + message=msg, + **kwd) ) + request.deleted = False + trans.sa_session.add( request ) + trans.sa_session.flush() return trans.response.send_redirect( web.url_for( controller='requests', action='list', status='done', - message='The request %s has been deleted.' % request.name, - **kwd) ) - def __undelete_request(self, trans, id): + message='%i request(s) has been undeleted.' % len(id_list) ) ) + def __submit_request(self, trans, **kwd): try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) - except: - msg = "Invalid request ID" - log.warn( msg ) - return trans.response.send_redirect( web.url_for( controller='requests', - action='list', - status='error', - message=msg, - **kwd) ) - # change request's submitted field - request.deleted = False - trans.sa_session.add( request ) - trans.sa_session.flush() - kwd = {} - kwd['id'] = trans.security.encode_id(request.id) - return trans.response.send_redirect( web.url_for( controller='requests', - action='list', - status='done', - message='The request %s has been undeleted.' % request.name, - **kwd) ) - def __submit_request(self, trans, id): - try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) except: msg = "Invalid request ID" log.warn( msg ) @@ -837,14 +885,12 @@ class Requests( BaseController ): request.state = request.states.SUBMITTED trans.sa_session.add( request ) trans.sa_session.flush() - kwd = {} - kwd['id'] = trans.security.encode_id(request.id) - kwd['status'] = 'done' - kwd['message'] = 'The request %s has been submitted.' % request.name return trans.response.send_redirect( web.url_for( controller='requests', action='list', - show_filter=trans.app.model.Request.states.SUBMITTED, - **kwd) ) + id=trans.security.encode_id(request.id), + status='done', + message='The request %s has been submitted.' % request.name + ) ) @web.expose @web.require_login( "create/submit sequencing requests" ) def show_events(self, trans, **kwd): diff --git a/lib/galaxy/web/controllers/requests_admin.py b/lib/galaxy/web/controllers/requests_admin.py index a58f7fe84d7..a11ca5f0e85 100644 --- a/lib/galaxy/web/controllers/requests_admin.py +++ b/lib/galaxy/web/controllers/requests_admin.py @@ -15,63 +15,187 @@ log = logging.getLogger( __name__ ) # ---- Request Grid ------------------------------------------------------------ # -class RequestsListGrid( grids.Grid ): +class RequestsGrid( grids.Grid ): + # Custom column types + class NameColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.name + class DescriptionColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.desc + class SamplesColumn( grids.GridColumn ): + def get_value(self, trans, grid, request): + return str(len(request.samples)) + class TypeColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.type.name + class LastUpdateColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.update_time + class StateColumn( grids.GridColumn ): + def filter( self, db_session, query, column_filter ): + """ Modify query to filter request by state. """ + if column_filter == "All": + return query + if column_filter: + query = query.filter( model.Request.state == column_filter ) + return query + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + accepted_filter_labels_and_vals = [ model.Request.states.UNSUBMITTED, + model.Request.states.SUBMITTED, + model.Request.states.COMPLETE, + "All"] + accepted_filters = [] + for val in accepted_filter_labels_and_vals: + label = val.lower() + args = { self.key: val } + accepted_filters.append( grids.GridColumnFilter( label, args) ) + return accepted_filters + class UserColumn( grids.TextColumn ): + def get_value(self, trans, grid, request): + return request.user.email + class DeletedColumn( grids.GridColumn ): + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + accepted_filter_labels_and_vals = { "active" : "False", "deleted" : "True", "all": "All" } + accepted_filters = [] + for label, val in accepted_filter_labels_and_vals.items(): + args = { self.key: val } + accepted_filters.append( grids.GridColumnFilter( label, args) ) + return accepted_filters + # Grid definition title = "Sequencing Requests" template = "admin/requests/grid.mako" model_class = model.Request default_sort_key = "-create_time" - show_filter = model.Request.states.SUBMITTED + num_rows_per_page = 50 + preserve_state = True + use_paging = True + default_filter = dict( deleted="False", state=model.Request.states.SUBMITTED) columns = [ - grids.GridColumn( "Name", key="name", - link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), - attach_popup=True ), - grids.GridColumn( "Description", key="desc"), - grids.GridColumn( "Sample(s)", method='number_of_samples', - link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), ), - grids.GridColumn( "Type", key="request_type_id", method='get_request_type'), - grids.GridColumn( "Last update", key="update_time", format=time_ago ), - grids.GridColumn( "State", key='state'), - grids.GridColumn( "User", key="user_id", method='get_user') - + NameColumn( "Name", + key="name", + model_class=model.Request, + link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), + attach_popup=True, + filterable="advanced" ), + DescriptionColumn( "Description", + key='desc', + model_class=model.Request, + filterable="advanced" ), + SamplesColumn( "Sample(s)", + link=( lambda item: iff( item.deleted, None, dict( operation="show_request", id=item.id ) ) ), ), + TypeColumn( "Type" ), + LastUpdateColumn( "Last update", + format=time_ago ), + StateColumn( "State", + key='state', + filterable="advanced"), + UserColumn( "User", + key='user.email', + model_class=model.Request, + filterable="advanced" ), + DeletedColumn( "Deleted", + key="deleted", + visible=True, + filterable="advanced" ) ] + columns.append( grids.MulticolFilterColumn( "Search", + cols_to_filter=[ columns[0], columns[1], columns[6] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) operations = [ grids.GridOperation( "Submit", allow_multiple=False, condition=( lambda item: not item.deleted and item.unsubmitted() and item.samples ) ), grids.GridOperation( "Edit", allow_multiple=False, condition=( lambda item: not item.deleted ) ), grids.GridOperation( "Reject", allow_multiple=False, condition=( lambda item: not item.deleted and item.submitted() ) ), - grids.GridOperation( "Delete", allow_multiple=False, condition=( lambda item: not item.deleted and item.unsubmitted() ) ), + grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted and item.unsubmitted() ) ), grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ), ] - standard_filters = [ - grids.GridColumnFilter( model.Request.states.UNSUBMITTED, - args=dict( state=model.Request.states.UNSUBMITTED, deleted=False ) ), - grids.GridColumnFilter( model.Request.states.SUBMITTED, - args=dict( state=model.Request.states.SUBMITTED, deleted=False ) ), - grids.GridColumnFilter( model.Request.states.COMPLETE, args=dict( state=model.Request.states.COMPLETE, deleted=False ) ), - grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), - grids.GridColumnFilter( "All", args=dict( deleted=False ) ) + global_actions = [ + grids.GridAction( "Create new request", dict( controller='requests_admin', + action='new', + select_request_type='True' ) ) ] - def get_user(self, trans, request): - return trans.sa_session.query( trans.app.model.User ).get( request.user_id ).email - def get_current_item( self, trans ): - return None - def get_request_type(self, trans, request): - request_type = trans.sa_session.query( trans.app.model.RequestType ).get( request.request_type_id ) - return request_type.name - def number_of_samples(self, trans, request): - return str(len(request.samples)) - def apply_default_filter( self, trans, query, **kwargs ): - if self.default_filter: - return query.filter_by( **self.default_filter ) - else: - return query - + +# +# ---- Request Type Gridr ------------------------------------------------------ +# +class RequestTypeGrid( grids.Grid ): + # Custom column types + class NameColumn( grids.TextColumn ): + def get_value(self, trans, grid, request_type): + return request_type.name + class DescriptionColumn( grids.TextColumn ): + def get_value(self, trans, grid, request_type): + return request_type.desc + class RequestFormColumn( grids.TextColumn ): + def get_value(self, trans, grid, request_type): + return request_type.request_form.name + class SampleFormColumn( grids.TextColumn ): + def get_value(self, trans, grid, request_type): + return request_type.sample_form.name + class DeletedColumn( grids.GridColumn ): + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + accepted_filter_labels_and_vals = { "active" : "False", "deleted" : "True", "all": "All" } + accepted_filters = [] + for label, val in accepted_filter_labels_and_vals.items(): + args = { self.key: val } + accepted_filters.append( grids.GridColumnFilter( label, args) ) + return accepted_filters + # Grid definition + title = "Requests Types" + template = "admin/requests/manage_request_types.mako" + model_class = model.RequestType + default_sort_key = "-create_time" + num_rows_per_page = 50 + preserve_state = True + use_paging = True + default_filter = dict( deleted="False" ) + columns = [ + NameColumn( "Name", + key="name", + model_class=model.RequestType, + link=( lambda item: iff( item.deleted, None, dict( operation="view", id=item.id ) ) ), + attach_popup=True, + filterable="advanced" ), + DescriptionColumn( "Description", + key='desc', + model_class=model.Request, + filterable="advanced" ), + RequestFormColumn( "Request Form", + link=( lambda item: iff( item.deleted, None, dict( operation="view_form", id=item.request_form.id ) ) ), ), + SampleFormColumn( "Sample Form", + link=( lambda item: iff( item.deleted, None, dict( operation="view_form", id=item.sample_form.id ) ) ), ), + DeletedColumn( "Deleted", + key="deleted", + visible=False, + filterable="advanced" ) + ] + columns.append( grids.MulticolFilterColumn( "Search", + cols_to_filter=[ columns[0], columns[1] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + operations = [ + #grids.GridOperation( "Update", allow_multiple=False, condition=( lambda item: not item.deleted ) ), + grids.GridOperation( "Delete", allow_multiple=True, condition=( lambda item: not item.deleted ) ), + grids.GridOperation( "Undelete", condition=( lambda item: item.deleted ) ), + ] + global_actions = [ + grids.GridAction( "Create new request type", dict( controller='requests_admin', + action='create_request_type' ) ) + ] # # ---- Request Controller ------------------------------------------------------ # class Requests( BaseController ): - request_grid = RequestsListGrid() + request_grid = RequestsGrid() + requesttype_grid = RequestTypeGrid() @web.expose @web.require_admin @@ -80,67 +204,59 @@ class Requests( BaseController ): @web.expose @web.require_admin - def list( self, trans, **kwargs ): + def list( self, trans, **kwd ): ''' List all request made by the current user ''' - message = util.restore_text( kwargs.get( 'message', '' ) ) - status = kwargs.get( 'status', 'done' ) - self.request_grid.default_filter = dict(state=trans.app.model.Request.states.SUBMITTED, - deleted=False) - if 'operation' in kwargs: - operation = kwargs['operation'].lower() + if 'operation' in kwd: + operation = kwd['operation'].lower() + if not kwd.get( 'id', None ): + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='list', + status='error', + message="Invalid request ID") ) if operation == "show_request": - id = trans.security.decode_id(kwargs['id']) - return self.__show_request(trans, id, status, message) + return self.__show_request( trans, **kwd ) elif operation == "submit": - id = trans.security.decode_id(kwargs['id']) - return self.__submit_request(trans, id) - elif operation == "edit": - id = trans.security.decode_id(kwargs['id']) - return self.__edit_request(trans, id) + return self.__submit_request( trans, **kwd ) elif operation == "delete": - id = trans.security.decode_id(kwargs['id']) - return self.__delete_request(trans, id) + return self.__delete_request( trans, **kwd ) elif operation == "undelete": - id = trans.security.decode_id(kwargs['id']) - return self.__undelete_request(trans, id) + return self.__undelete_request( trans, **kwd ) + elif operation == "edit": + return self.__edit_request( trans, **kwd ) elif operation == "reject": - id = trans.security.decode_id(kwargs['id']) - return self.__reject_request(trans, id) - if 'show_filter' in kwargs.keys(): - if kwargs['show_filter'] == 'All': - self.request_grid.default_filter = {} - elif kwargs['show_filter'] == 'Deleted': - self.request_grid.default_filter = dict(deleted=True) - else: - self.request_grid.default_filter = dict(state=kwargs['show_filter'], deleted=False) - self.request_grid.show_filter = kwargs.get('show_filter', trans.app.model.Request.states.SUBMITTED) - # Render the list view - return self.request_grid( trans, **kwargs ) - def __show_request(self, trans, id, messagetype, msg): + return self.__reject_request( trans, **kwd ) + # Render the grid view + return self.request_grid( trans, **kwd ) + def __show_request(self, trans, **kwd): + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + add_sample = params.get('add_sample', False) try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) except: return trans.response.send_redirect( web.url_for( controller='requests_admin', action='list', status='error', - message="Invalid request ID", - **kwd) ) + message="Invalid request ID") ) current_samples = [] for s in request.samples: current_samples.append([s.name, s.values.content]) + if add_sample: + current_samples.append(['Sample_%i' % (len(current_samples)+1),['' for field in request.type.sample_form.fields]]) return trans.fill_template( '/admin/requests/show_request.mako', request=request, - request_details=self.request_details(trans, id), + request_details=self.request_details(trans, request.id), current_samples = current_samples, sample_copy=self.__copy_sample(current_samples), details='hide', edit_mode='False', - msg=msg, messagetype=messagetype) + msg=msg, messagetype=messagetype ) - def __edit_request(self, trans, id, **kwd): + def __edit_request(self, trans, **kwd): try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) except: msg = "Invalid request ID" log.warn( msg ) @@ -180,61 +296,61 @@ class Requests( BaseController ): msg=msg, messagetype=messagetype) return self.__show_request_form(trans) - def __delete_request(self, trans, id): - try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) - except: - msg = "Invalid request ID" - log.warn( msg ) - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message=msg, - **kwd) ) - # change request's submitted field - if not request.unsubmitted(): - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message='This request cannot be deleted as it is already been submitted', - **kwd) ) - request.deleted = True - trans.sa_session.add( request ) - trans.sa_session.flush() - kwd = {} - kwd['id'] = trans.security.encode_id(request.id) + def __delete_request(self, trans, **kwd): + id_list = util.listify( kwd['id'] ) + delete_failed = [] + for id in id_list: + try: + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) ) + except: + msg = "Invalid request ID" + log.warn( msg ) + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='list', + status='error', + message=msg, + **kwd) ) + # a request cannot be deleted once its submitted + if not request.unsubmitted(): + delete_failed.append(request.name) + else: + request.deleted = True + trans.sa_session.add( request ) + trans.sa_session.flush() + if not len(delete_failed): + msg = '%i request(s) has been deleted.' % len(id_list) + status = 'done' + else: + msg = '%i request(s) has been deleted. %i request %s could not be deleted as they have been submitted.' % (len(id_list)-len(delete_failed), + len(delete_failed), str(delete_failed)) + status = 'warning' return trans.response.send_redirect( web.url_for( controller='requests_admin', action='list', - show_filter=trans.app.model.Request.states.UNSUBMITTED, - status='done', - message='The request %s has been deleted.' % request.name, - **kwd) ) - def __undelete_request(self, trans, id): - try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) - except: - msg = "Invalid request ID" - log.warn( msg ) - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message=msg, - **kwd) ) - # change request's submitted field - request.deleted = False - trans.sa_session.add( request ) - trans.sa_session.flush() - kwd = {} - kwd['id'] = trans.security.encode_id(request.id) + status=status, + message=msg) ) + def __undelete_request(self, trans, **kwd): + id_list = util.listify( kwd['id'] ) + for id in id_list: + try: + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(id) ) + except: + msg = "Invalid request ID" + log.warn( msg ) + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='list', + status='error', + message=msg, + **kwd) ) + request.deleted = False + trans.sa_session.add( request ) + trans.sa_session.flush() return trans.response.send_redirect( web.url_for( controller='requests_admin', action='list', - show_filter=trans.app.model.Request.states.UNSUBMITTED, status='done', - message='The request %s has been undeleted.' % request.name, - **kwd) ) - def __submit_request(self, trans, id): + message='%i request(s) has been undeleted.' % len(id_list) ) ) + def __submit_request(self, trans, **kwd): try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) except: msg = "Invalid request ID" log.warn( msg ) @@ -260,15 +376,32 @@ class Requests( BaseController ): request.state = request.states.SUBMITTED trans.sa_session.add( request ) trans.sa_session.flush() - kwd = {} - kwd['id'] = trans.security.encode_id(request.id) - kwd['status'] = 'done' - kwd['message'] = 'The request %s has been submitted.' % request.name return trans.response.send_redirect( web.url_for( controller='requests_admin', action='list', - show_filter=trans.app.model.Request.states.SUBMITTED, - **kwd) ) - + id=trans.security.encode_id(request.id), + status='done', + message='The request %s has been submitted.' % request.name + ) ) + def __reject_request(self, trans, **kwd): + try: + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) + except: + msg = "Invalid request ID" + log.warn( msg ) + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='list', + status='error', + message=msg, + **kwd) ) + # change request's submitted field + request.state = request.states.UNSUBMITTED + trans.sa_session.add( request ) + trans.sa_session.flush() + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='list', + status='done', + message='The request %s is now unsubmitted.' % request.name + ) ) # #---- Request Creation ---------------------------------------------------------- # @@ -325,7 +458,6 @@ class Requests( BaseController ): if params.get('create_request_button', False) == 'Save': return trans.response.send_redirect( web.url_for( controller='requests_admin', action='list', - show_filter=trans.app.model.Request.states.UNSUBMITTED, message=msg , status='done') ) elif params.get('create_request_samples_button', False) == 'Add samples': @@ -664,29 +796,6 @@ class Requests( BaseController ): **new_kwd) ) elif params.get('refresh', False) == 'true': return self.__edit_request(trans, request.id, **kwd) - def __reject_request(self, trans, id): - try: - request = trans.sa_session.query( trans.app.model.Request ).get( id ) - except: - msg = "Invalid request ID" - log.warn( msg ) - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message=msg, - **kwd) ) - # change request's submitted field - request.state = request.states.UNSUBMITTED - trans.sa_session.add( request ) - trans.sa_session.flush() - kwd = {} - kwd['id'] = trans.security.encode_id(request.id) - kwd['status'] = 'done' - kwd['message'] = 'The request %s is now unsubmitted.' % request.name - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - show_filter=trans.app.model.Request.states.UNSUBMITTED, - **kwd) ) def __update_samples(self, request, **kwd): ''' This method retrieves all the user entered sample information and @@ -1049,8 +1158,8 @@ class Requests( BaseController ): action='list', operation='show_request', id=trans.security.encode_id(request.id), - message='Bar codes have been saved for this request', - status='done')) + msg='Bar codes have been saved for this request', + messagetype='done')) def __set_request_state( self, trans, request ): # check if all the samples of the current request are in the final state complete = True @@ -1138,50 +1247,72 @@ class Requests( BaseController ): ## @web.expose @web.require_admin - def manage_request_types( self, trans, **kwd ): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - show_filter = util.restore_text( params.get( 'show_filter', 'Active' ) ) - forms = get_all_forms(trans, all_versions=True) - request_types_list = trans.sa_session.query( trans.app.model.RequestType ) - if show_filter == 'All': - request_types = request_types_list - elif show_filter == 'Deleted': - request_types = [rt for rt in request_types_list if rt.deleted] - else: - request_types = [rt for rt in request_types_list if not rt.deleted] - return trans.fill_template( '/admin/requests/manage_request_types.mako', - request_types=request_types, - forms=forms, - show_filter=show_filter, - msg=msg, - messagetype=messagetype ) + def manage_request_types( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() + if not kwd.get( 'id', None ): + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_request_types', + status='error', + message="Invalid requesttype ID") ) + if operation == "view": + return self.__view_request_type( trans, **kwd ) + elif operation == "view_form": + return self.__view_form( trans, **kwd ) + elif operation == "delete": + return self.__delete_request_type( trans, **kwd ) + elif operation == "undelete": + return self.__undelete_request_type( trans, **kwd ) +# elif operation == "update": +# return self.__edit_request( trans, **kwd ) + # Render the grid view + return self.requesttype_grid( trans, **kwd ) + def __view_request_type(self, trans, **kwd): + try: + rt = trans.sa_session.query( trans.app.model.RequestType ).get( trans.security.decode_id(kwd['id']) ) + except: + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_request_types', + status='error', + message="Invalid requesttype ID") ) + return trans.fill_template( '/admin/requests/view_request_type.mako', + request_type=rt, + forms=get_all_forms( trans ), + states_list=rt.states ) + def __view_form(self, trans, **kwd): + try: + fd = trans.sa_session.query( trans.app.model.FormDefinition ).get( trans.security.decode_id(kwd['id']) ) + except: + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_request_types', + status='error', + message="Invalid form ID") ) + return trans.fill_template( '/admin/forms/show_form_read_only.mako', + form=fd ) + @web.expose @web.require_admin - def request_type( self, trans, **kwd ): + def create_request_type( self, trans, **kwd ): params = util.Params( kwd ) msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) - if params.get( 'create', False ): + if params.get( 'add_state_button', False ): + rt_info, rt_states = self.__create_request_type_form(trans, **kwd) + rt_states.append(("", "")) return trans.fill_template( '/admin/requests/create_request_type.mako', - request_forms=get_all_forms( trans, - filter=dict(deleted=False), - form_type=trans.app.model.FormDefinition.types.REQUEST ), - sample_forms=get_all_forms( trans, - filter=dict(deleted=False), - form_type=trans.app.model.FormDefinition.types.SAMPLE ), + rt_info_widgets=rt_info, + rt_states_widgets=rt_states, msg=msg, messagetype=messagetype) - elif params.get( 'define_states_button', False ): - return trans.fill_template( '/admin/requests/add_states.mako', - request_type_name=util.restore_text( params.name ), - desc=util.restore_text( params.description ), - num_states=int(util.restore_text( params.num_states )), - request_form_id=int(util.restore_text( params.request_form_id )), - sample_form_id=int(util.restore_text( params.sample_form_id )), + elif params.get( 'remove_state_button', False ): + rt_info, rt_states = self.__create_request_type_form(trans, **kwd) + index = int(params.get( 'remove_state_button', '' ).split(" ")[2]) + del rt_states[index-1] + return trans.fill_template( '/admin/requests/create_request_type.mako', + rt_info_widgets=rt_info, + rt_states_widgets=rt_states, msg=msg, - messagetype=messagetype) + messagetype=messagetype) elif params.get( 'save_request_type', False ): st, msg = self.__save_request_type(trans, **kwd) if not st: @@ -1191,32 +1322,65 @@ class Requests( BaseController ): messagetype='error') return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_request_types', - msg='Request type %s has been created' % st.name, - messagetype='done') ) - elif params.get('view', False): - rt = trans.sa_session.query( trans.app.model.RequestType ).get( int( util.restore_text( params.id ) ) ) - return trans.fill_template( '/admin/requests/view_request_type.mako', - request_type=rt, - forms=get_all_forms( trans ), - states_list=rt.states, - deleted=False, - show_deleted=False, + message='Request type %s has been created' % st.name, + status='done') ) + else: + rt_info, rt_states = self.__create_request_type_form(trans, **kwd) + return trans.fill_template( '/admin/requests/create_request_type.mako', + rt_info_widgets=rt_info, + rt_states_widgets=rt_states, msg=msg, - messagetype=messagetype ) + messagetype=messagetype) + def __create_request_type_form(self, trans, **kwd): + request_forms=get_all_forms( trans, + filter=dict(deleted=False), + form_type=trans.app.model.FormDefinition.types.REQUEST ) + sample_forms=get_all_forms( trans, + filter=dict(deleted=False), + form_type=trans.app.model.FormDefinition.types.SAMPLE ) + if not len(request_forms) or not len(sample_forms): + return [],[] + params = util.Params( kwd ) + rt_info = [] + rt_info.append(dict(label='Name', + widget=TextField('name', 40, util.restore_text( params.get( 'name', '' ) ) ) )) + rt_info.append(dict(label='Description', + widget=TextField('desc', 40, util.restore_text( params.get( 'desc', '' ) ) ) )) + + rf_selectbox = SelectField('request_form_id') + for fd in request_forms: + if str(fd.id) == params.get( 'request_form_id', '' ): + rf_selectbox.add_option(fd.name, fd.id, selected=True) + else: + rf_selectbox.add_option(fd.name, fd.id) + rt_info.append(dict(label='Request form', + widget=rf_selectbox )) + + sf_selectbox = SelectField('sample_form_id') + for fd in sample_forms: + if str(fd.id) == params.get( 'sample_form_id', '' ): + sf_selectbox.add_option(fd.name, fd.id, selected=True) + else: + sf_selectbox.add_option(fd.name, fd.id) + rt_info.append(dict(label='Sample form', + widget=sf_selectbox )) + # possible sample states + rt_states = [] + i=0 + while True: + if kwd.has_key( 'state_name_%i' % i ): + rt_states.append((params.get( 'state_name_%i' % i, '' ), + params.get( 'state_desc_%i' % i, '' ))) + i=i+1 + else: + break + return rt_info, rt_states + def __save_request_type(self, trans, **kwd): params = util.Params( kwd ) - num_states = int( util.restore_text( params.get( 'num_states', 0 ) )) - proceed = True - for i in range( num_states ): - if not util.restore_text( params.get( 'state_name_%i' % i, None ) ): - proceed = False - break - if not proceed: - msg = "All the state name(s) must be completed." - return None, msg rt = trans.app.model.RequestType() - rt.name = util.restore_text( params.name ) - rt.desc = util.restore_text( params.description ) or "" + rt.name = util.restore_text( params.get( 'name', '' ) ) + rt.desc = util.restore_text( params.get( 'desc', '' ) ) rt.request_form = trans.sa_session.query( trans.app.model.FormDefinition ).get( int( params.request_form_id ) ) rt.sample_form = trans.sa_session.query( trans.app.model.FormDefinition ).get( int( params.sample_form_id ) ) trans.sa_session.add( rt ) @@ -1226,37 +1390,50 @@ class Requests( BaseController ): for ss in ss_list: trans.sa_session.delete( ss ) trans.sa_session.flush() - for i in range( num_states ): - name = util.restore_text( params.get( 'state_name_%i' % i, None )) - desc = util.restore_text( params.get( 'state_desc_%i' % i, None )) - ss = trans.app.model.SampleState(name, desc, rt) - trans.sa_session.add( ss ) - trans.sa_session.flush() - msg = "The new request type named '%s' with %s state(s) has been created" % (rt.name, num_states) + i=0 + while True: + if kwd.has_key( 'state_name_%i' % i ): + name = util.restore_text( params.get( 'state_name_%i' % i, None )) + desc = util.restore_text( params.get( 'state_desc_%i' % i, None )) + ss = trans.app.model.SampleState(name, desc, rt) + trans.sa_session.add( ss ) + trans.sa_session.flush() + i = i + 1 + else: + break + msg = "The new request type named '%s' with %s state(s) has been created" % (rt.name, i) return rt, msg - @web.expose - @web.require_admin - def delete_request_type( self, trans, **kwd ): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - rt = trans.sa_session.query( trans.app.model.RequestType ).get( int( util.restore_text( params.request_type_id ) ) ) - rt.deleted = True - trans.sa_session.flush() + def __delete_request_type( self, trans, **kwd ): + id_list = util.listify( kwd['id'] ) + for id in id_list: + try: + rt = trans.sa_session.query( trans.app.model.RequestType ).get( trans.security.decode_id(id) ) + except: + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_request_types', + msg='Invalid request type ID', + messagetype='error') ) + rt.deleted = True + trans.sa_session.add( rt ) + trans.sa_session.flush() return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_request_types', - msg='Request type %s has been deleted' % rt.name, + msg='%i request type(s) has been deleted' % len(id_list), messagetype='done') ) - @web.expose - @web.require_admin - def undelete_request_type( self, trans, **kwd ): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - rt = trans.sa_session.query( trans.app.model.RequestType ).get( int( util.restore_text( params.request_type_id ) ) ) - rt.deleted = False - trans.sa_session.flush() + def __undelete_request_type( self, trans, **kwd ): + id_list = util.listify( kwd['id'] ) + for id in id_list: + try: + rt = trans.sa_session.query( trans.app.model.RequestType ).get( trans.security.decode_id(id) ) + except: + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_request_types', + msg='Invalid request type ID', + messagetype='error') ) + rt.deleted = False + trans.sa_session.add( rt ) + trans.sa_session.flush() return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_request_types', - msg='Request type %s has been undeleted' % rt.name, + msg='%i request type(s) has been undeleted' % len(id_list), messagetype='done') ) diff --git a/lib/galaxy/web/controllers/user.py b/lib/galaxy/web/controllers/user.py index a017ff97143..f056d3a584c 100644 --- a/lib/galaxy/web/controllers/user.py +++ b/lib/galaxy/web/controllers/user.py @@ -181,9 +181,7 @@ class User( BaseController ): refresh_frames = [ 'masthead', 'history' ] if not trans.app.config.allow_user_creation and not trans.user_is_admin(): return trans.show_error_message( 'User registration is disabled. Please contact your Galaxy administrator for an account.' ) - # # Create the user, save all the user info and login to Galaxy - # if params.get('create_user_button', None) == "Submit": # check email and password validity error = self.__validate(trans, params, email, password, confirm) diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index 81fe01c5bf6..5efec6a0bdc 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -446,13 +446,11 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): self.sa_session.flush() # This method is not called from the Galaxy reports, so the cookie will always be galaxysession self.__update_session_cookie( name='galaxysession' ) - def get_galaxy_session( self ): """ Return the current galaxy session """ return self.galaxy_session - def get_history( self, create=False ): """ Load the current history, creating a new one only if there is not @@ -497,7 +495,6 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): self.sa_session.add_all( ( self.galaxy_session, history ) ) self.sa_session.flush() return history - def get_user( self ): """Return the current user if logged in or None.""" return self.galaxy_session.user @@ -507,7 +504,6 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): self.sa_session.add( self.galaxy_session ) self.sa_session.flush() user = property( get_user, set_user ) - def get_user_and_roles( self ): user = self.get_user() if user: @@ -515,13 +511,9 @@ class UniverseWebTransaction( base.DefaultWebTransaction ): else: roles = [] return user, roles - def user_is_admin( self ): admin_users = self.app.config.get( "admin_users", "" ).split( "," ) - if self.user and admin_users and self.user.email in admin_users: - return True - return False - + return self.user and admin_users and self.user.email in admin_users def get_toolbox(self): """Returns the application toolbox""" return self.app.toolbox diff --git a/lib/galaxy/web/framework/helpers/grids.py b/lib/galaxy/web/framework/helpers/grids.py index 803e6bf5f15..16af3ff17ff 100644 --- a/lib/galaxy/web/framework/helpers/grids.py +++ b/lib/galaxy/web/framework/helpers/grids.py @@ -1,6 +1,7 @@ from galaxy.model import * from galaxy.model.orm import * +from galaxy.web.framework.helpers import iff from galaxy.tags.tag_handler import TagHandler from galaxy.web import url_for from galaxy.util.json import from_json_string, to_json_string @@ -16,7 +17,9 @@ class Grid( object ): title = "" exposed = True model_class = None + # To use grid's async features, set template="grid_base_async.mako" template = "grid_base.mako" + async_template = "grid_body_async.mako" global_actions = [] columns = [] operations = [] @@ -213,7 +216,7 @@ class Grid( object ): return url_for( **new_kwargs ) - return trans.fill_template( self.template, + return trans.fill_template( iff( 'async' not in kwargs, self.template, self.async_template), grid=self, query=query, cur_page_num = page_num, @@ -390,7 +393,7 @@ class MulticolFilterColumn( TextColumn ): return query.filter( complete_filter ) class GridOperation( object ): - def __init__( self, label, key=None, condition=None, allow_multiple=True, allow_popup=True, target=None, url_args=None ): + def __init__( self, label, key=None, condition=None, allow_multiple=True, allow_popup=True, target=None, url_args=None, async_compatible=False ): self.label = label self.key = key self.allow_multiple = allow_multiple @@ -398,6 +401,7 @@ class GridOperation( object ): self.condition = condition self.target = target self.url_args = url_args + self.async_compatible = async_compatible def get_url_args( self, item ): if self.url_args: temp = dict( self.url_args ) diff --git a/run.sh b/run.sh index fe49fbef491..cddfde470fe 100755 --- a/run.sh +++ b/run.sh @@ -1,4 +1,23 @@ #!/bin/sh cd `dirname $0` + +# explicitly attempt to fetch eggs before running +FETCH_EGGS=1 +for arg in "$@"; do + [ "$arg" = "--stop-daemon" ] && FETCH_EGGS=0; break +done +if [ $FETCH_EGGS -eq 1 ]; then + python -ES ./scripts/check_eggs.py quiet + if [ $? -ne 0 ]; then + echo "Some eggs are out of date, attempting to fetch..." + python -ES ./scripts/fetch_eggs.py + if [ $? -eq 0 ]; then + echo "Fetch successful." + else + echo "Fetch failed." + exit 1 + fi + fi +fi python -ES ./scripts/paster.py serve universe_wsgi.ini $@ diff --git a/scripts/check_eggs.py b/scripts/check_eggs.py index 578be4477e7..09a77cb1e6f 100644 --- a/scripts/check_eggs.py +++ b/scripts/check_eggs.py @@ -11,6 +11,12 @@ root.addHandler( logging.StreamHandler( sys.stdout ) ) lib = os.path.abspath( os.path.join( os.path.dirname( __file__ ), "..", "lib" ) ) sys.path.append( lib ) +try: + assert sys.argv[1] == 'quiet' + quiet = True +except: + quiet = False + from galaxy.eggs import * c = Crate() @@ -21,7 +27,9 @@ for name in c.get_names(): if not galaxy_config.check_conditional( name ): ignore.append( name ) if not c.find( ignore=ignore ): - print "Some of your Galaxy eggs are out of date. Please update them" - print "by running:" - print " python scripts/fetch_eggs.py" + if not quiet: + print "Some of your Galaxy eggs are out of date. Please update them" + print "by running:" + print " python scripts/fetch_eggs.py" sys.exit( 1 ) +sys.exit( 0 ) diff --git a/scripts/check_python.py b/scripts/check_python.py index 69f6f464323..1788352732d 100644 --- a/scripts/check_python.py +++ b/scripts/check_python.py @@ -1,19 +1,16 @@ import os, sys +msg = """ERROR: Your Python version is: %s +Galaxy is currently supported on Python 2.4, 2.5 and 2.6. To run Galaxy, +please download and install a supported version from python.org. If a +supported version is installed but is not your default, getgalaxy.org +contains instructions on how to force Galaxy to use a different version.""" % sys.version[:3] + def check_python(): - return try: - assert sys.version_info[:2] >= ( 2, 4 ) and sys.version_info[:2] <= ( 2, 5 ) + assert sys.version_info[:2] >= ( 2, 4 ) and sys.version_info[:2] <= ( 2, 6 ) except AssertionError: - print >>sys.stderr, "ERROR: Your Python version is:", sys.version.split( ' ', 1 )[0] - print >>sys.stderr, "Galaxy is currently only supported on Python 2.4 and Python 2.5." - if sys.version_info[:2] < ( 2, 4 ): - print >>sys.stderr, "To run Galaxy, please download and install Python 2.5 from http://python.org" - else: - print >>sys.stderr, "To track the progress of Python 2.6 support, please see:" - print >>sys.stderr, " http://bitbucket.org/galaxy/galaxy-central/issue/76/support-python-26" - print >>sys.stderr, "For hints on how to direct Galaxy to use a different python installation, see:" - print >>sys.stderr, " http://bitbucket.org/galaxy/galaxy-central/wiki/GetGalaxy" + print >>sys.stderr, msg raise if __name__ == '__main__': diff --git a/scripts/cleanup_datasets/cleanup_datasets.py b/scripts/cleanup_datasets/cleanup_datasets.py index 0cd7fed5063..a9606be76d6 100644 --- a/scripts/cleanup_datasets/cleanup_datasets.py +++ b/scripts/cleanup_datasets/cleanup_datasets.py @@ -276,7 +276,7 @@ def purge_datasets( app, cutoff_time, remove_from_disk, info_only = False, force app.model.Dataset.table.c.update_time < cutoff_time ) ) for dataset in datasets: file_size = dataset.file_size - _purge_dataset( dataset, remove_from_disk, info_only = info_only ) + _purge_dataset( app, dataset, remove_from_disk, info_only = info_only ) dataset_count += 1 try: disk_space += file_size @@ -349,7 +349,7 @@ def _delete_dataset( dataset, app, remove_from_disk, info_only=False, is_deletab app.sa_session.add( dataset ) app.sa_session.flush() -def _purge_dataset( dataset, remove_from_disk, info_only = False ): +def _purge_dataset( app, dataset, remove_from_disk, info_only = False ): if dataset.deleted: try: if dataset.purgable and _dataset_is_deletable( dataset ): diff --git a/scripts/scramble/scripts/pysqlite.py b/scripts/scramble/scripts/pysqlite.py index 0c97eee5256..fc8d2b2b2c8 100644 --- a/scripts/scramble/scripts/pysqlite.py +++ b/scripts/scramble/scripts/pysqlite.py @@ -1,18 +1,4 @@ -import os, sys, subprocess, tarfile, zipfile, shutil - -def unpack_sqlite_source(): - print "unpack_sqlite_source(): Found a previously downloaded sqlite source." - print "unpack_sqlite_source(): To force a new download, remove the archive:" - print " ", SQLITE_ARCHIVE - os.makedirs( "sqlite" ) - z = zipfile.ZipFile( SQLITE_ARCHIVE, "r" ) - for fn in z.namelist(): - if fn == "tclsqlite.c" or fn == "shell.c" or fn == "icu.c": - continue - o = open( os.path.join( "sqlite", fn ), "wb" ) - o.write( z.read( fn ) ) - o.close() - z.close() +import os, sys, shutil # change back to the build dir if os.path.dirname( sys.argv[0] ) != "": @@ -38,42 +24,16 @@ if os.access( ".galaxy_tag", os.F_OK ): else: tag = None -SQLITE_VERSION = ( tag.split( "_" ) )[1] -SQLITE_ARCHIVE = os.path.abspath( os.path.join( "..", "..", "..", "archives", "sqlite-source-%s.zip" %SQLITE_VERSION.replace( ".", "_" ) ) ) - # clean, in case you're running this by hand from a dirty module source dir for dir in [ "build", "dist", "sqlite" ]: if os.access( dir, os.F_OK ): print "scramble_it.py: removing dir:", dir shutil.rmtree( dir ) -# build/unpack SQLite -unpack_sqlite_source() - -# changes to setup.py -file = "setup.py" -print "build(): Patching", file -if not os.access( "%s.orig" %file, os.F_OK ): - shutil.copyfile( file, "%s.orig" %file ) -i = open( "%s.orig" %file, "r" ) -o = open( file, "w" ) -for line in i.readlines(): - if line == ' "src/util.c", "src/row.c"]\n': - line += 'sources += glob.glob("./sqlite/*.c")\n' - if line == "include_dirs = []\n": - line = "include_dirs = [ './sqlite' ]\n" - if line == "define_macros = []\n": - line = "define_macros = [ ('THREADSAFE','1') ]\n" - print >>o, line, -i.close() -o.close() - -# don't want setup.cfg -os.rename( "setup.cfg", "setup.cfg.orig" ) - # tag me = sys.argv[0] sys.argv = [ me ] +sys.argv.append( "build_static" ) if tag is not None: sys.argv.append( "egg_info" ) sys.argv.append( "--tag-build=%s" %tag ) diff --git a/scripts/set_metadata.py b/scripts/set_metadata.py index c0bea59d325..5c14dc71ada 100644 --- a/scripts/set_metadata.py +++ b/scripts/set_metadata.py @@ -25,6 +25,7 @@ import galaxy.model.mapping #need to load this before we unpickle, in order to s galaxy.model.Job() #this looks REAL stupid, but it is REQUIRED in order for SA to insert parameters into the classes defined by the mappers --> it appears that instantiating ANY mapper'ed class would suffice here galaxy.datatypes.metadata.DATABASE_CONNECTION_AVAILABLE = False #Let metadata know that there is no database connection, and to just assume object ids are valid from galaxy.util import stringify_dictionary_keys +from galaxy.util.json import from_json_string from sqlalchemy.orm import clear_mappers def __main__(): @@ -37,13 +38,25 @@ def __main__(): config_root = sys.argv.pop( 1 ) datatypes_config = sys.argv.pop( 1 ) galaxy.model.set_datatypes_registry( galaxy.datatypes.registry.Registry( config_root, datatypes_config ) ) - + + job_metadata = sys.argv.pop( 1 ) + ext_override = dict() + if job_metadata != "None" and os.path.exists( job_metadata ): + for line in open( job_metadata, 'r' ): + try: + line = stringify_dictionary_keys( from_json_string( line ) ) + assert line['type'] == 'dataset' + ext_override[line['dataset_id']] = line['ext'] + except: + continue for filenames in sys.argv[1:]: filename_in, filename_kwds, filename_out, filename_results_code, dataset_filename_override = filenames.split( ',' ) try: dataset = cPickle.load( open( filename_in ) ) #load DatasetInstance if dataset_filename_override: dataset.dataset.external_filename = dataset_filename_override + if ext_override.get( dataset.dataset.id, None ): + dataset.extension = ext_override[ dataset.dataset.id ] kwds = stringify_dictionary_keys( simplejson.load( open( filename_kwds ) ) )#load kwds; need to ensure our keywords are not unicode dataset.datatype.set_meta( dataset, **kwds ) dataset.metadata.to_JSON_dict( filename_out ) # write out results of set_meta diff --git a/static/scripts/galaxy.base.js b/static/scripts/galaxy.base.js index a19b877fbbc..aaeca1a1329 100644 --- a/static/scripts/galaxy.base.js +++ b/static/scripts/galaxy.base.js @@ -19,6 +19,12 @@ jQuery(document).ready( function() { jQuery( "a[confirm]" ).click( function() { return confirm( jQuery(this).attr( "confirm" ) ) }); + // Make popup menus. + make_popup_menus(); +}); + +function make_popup_menus() +{ jQuery( "div[popupmenu]" ).each( function() { var options = {}; $(this).find( "a" ).each( function() { @@ -40,7 +46,7 @@ jQuery(document).ready( function() { $(this).remove(); b.show(); }); -}); +} function ensure_popup_helper() { // And the helper below the popup menus @@ -103,3 +109,15 @@ function attach_popupmenu( button_element, wrapper ) { }; $( button_element ).click( click ); }; + +// Returns the number of keys (elements) in an array/dictionary. +var array_length = function(an_array) +{ + if (an_array.length) + return an_array.length; + + var count = 0; + for (element in an_array) + count++; + return count; +}; diff --git a/templates/admin/forms/edit_form.mako b/templates/admin/forms/edit_form.mako index 0a910956a77..853e080895c 100644 --- a/templates/admin/forms/edit_form.mako +++ b/templates/admin/forms/edit_form.mako @@ -85,7 +85,7 @@ $( function() {
Edit form definition "${form.name}"
-
+ %for label, input in form_details:
diff --git a/templates/admin/forms/grid.mako b/templates/admin/forms/grid.mako new file mode 100644 index 00000000000..6a41b688c5e --- /dev/null +++ b/templates/admin/forms/grid.mako @@ -0,0 +1 @@ +<%inherit file="/grid_base.mako"/> diff --git a/templates/admin/forms/manage_forms.mako b/templates/admin/forms/manage_forms.mako deleted file mode 100644 index e1ed9256fee..00000000000 --- a/templates/admin/forms/manage_forms.mako +++ /dev/null @@ -1,76 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -<%def name="title()">Manage Form Definitions - -%if msg: - ${render_msg( msg, messagetype )} -%endif - -## Render a row -<%def name="render_row( form, ctr )"> - %if ctr % 2 == 1: -
- %else: - - %endif - - - - - - -

Forms

- - - -%if not all_forms: - There are no forms. -%else: -
- %for i, filter in enumerate( ['Active', 'Deleted', 'All'] ): - %if i > 0: - | - %endif - %if show_filter == filter: - ${filter} - %else: - ${filter} - %endif - %endfor -
-
- ${form.name} - - %if form.form_definition_current.deleted: -
- Undelete -
- %else: -
- Edit - Delete -
- %endif -
${form.desc}${form.type}
- - - - - - - - - %for ctr, fdc in enumerate( fdc_list ): - - ${render_row( fdc.latest_form, ctr )} - - %endfor - -
NameDescriptionType
-%endif diff --git a/templates/admin/forms/show_form_read_only.mako b/templates/admin/forms/show_form_read_only.mako index ad2f4a5afb5..4b576c15b34 100644 --- a/templates/admin/forms/show_form_read_only.mako +++ b/templates/admin/forms/show_form_read_only.mako @@ -66,14 +66,14 @@
${form.name} - ${form.desc} (${form.type})
- Edit + Edit
%else:
${form.name} (${form.type})
- Edit + Edit
%endif diff --git a/templates/admin/requests/create_request_type.mako b/templates/admin/requests/create_request_type.mako index f42bff94833..83c52307f88 100644 --- a/templates/admin/requests/create_request_type.mako +++ b/templates/admin/requests/create_request_type.mako @@ -6,63 +6,59 @@ ${render_msg( msg, messagetype )} %endif +<%def name="render_state( element_count, state_name, state_desc )"> +
+
+ + + +
+
+ + +
+ optional +
+ +
+
+
+ +
Create a new request type
- %if not request_forms or not sample_forms: + %if not rt_info_widgets: Create a request & sample form definition first to create a new request type. %else: -
- + + %for rt_info in rt_info_widgets:
- +
- + ${rt_info['widget'].get_html()}
-
- -
- -
-
+ %endfor +
Possible sample states
+ %if len(rt_states_widgets): + %for index, info in enumerate(rt_states_widgets): + ${render_state( index, info[0], info[1] )} + %endfor + %endif +
+ +
+
+
+
-
- - -
-
- - -
-
- -
- -
-
-
-
- -
-
-
-
- -
- -
+
+
+
+ +
+ + %endif
\ No newline at end of file diff --git a/templates/admin/requests/grid.mako b/templates/admin/requests/grid.mako index b4f601fe8e6..8fc475526ee 100644 --- a/templates/admin/requests/grid.mako +++ b/templates/admin/requests/grid.mako @@ -1,217 +1 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -<%def name="title()">Browse Requests - -%if message: -

-

${message}
-
-

-%endif - -<%def name="javascripts()"> - ${parent.javascripts()} - - - -<%def name="stylesheets()"> - - - - -
-

${grid.title}

- %if trans.sa_session.query( trans.app.model.Request ).count(): - ##Filter: - %for i, filter in enumerate( grid.standard_filters ): - %if i > 0: - | - %endif - %if grid.show_filter == filter.label: - ${filter.label} - %else: - ${filter.label} - %endif - %endfor - %endif -
- - - -%if not len(query.all()): - There are no requests. -%else: -
- - - - ## - %for column in grid.columns: - %if column.visible: - <% - href = "" - extra = "" - if column.sortable: - if sort_key == column.key: - if sort_order == "asc": - href = url( sort=( "-" + column.key ) ) - extra = "↓" - else: - href = url( sort=( column.key ) ) - extra = "↑" - else: - href = url( sort=column.key ) - %> - 1: - colspan="${column.ncells}" - %endif - > - %if href: - ${column.label} - %else: - ${column.label} - %endif - ${extra} - - %endif - %endfor - - - - - %for i, item in enumerate( query ): - - ## Item selection column - ## - ## Data columns - %for column in grid.columns: - %if column.visible: - <% - # Link - if column.link and column.link( item ): - href = url( **column.link( item ) ) - else: - href = None - # Value (coerced to list so we can loop) - value = column.get_value( trans, grid, item ) - if column.ncells == 1: - value = [ value ] - %> - %for cellnum, v in enumerate( value ): - <% - # Attach popup menu? - if column.attach_popup and cellnum == 0: - extra = '' % i - else: - extra = "" - %> - %if href: - - %else: - - %endif - - %endfor - %endif - %endfor - ## Actions column - - - %endfor - -## -## -## -## -## -## -
- ## - ##${v} ${extra}${v}${extra} -
- %for operation in grid.operations: - %if operation.allowed( item ): - %if operation.label == 'Submit': - ${operation.label} - %else: - ${operation.label} - %endif - %endif - %endfor -
-
-## For selected requests: -## %for operation in grid.operations: -## %if operation.allow_multiple: -## -## %endif -## %endfor -##
-
-%endif \ No newline at end of file +<%inherit file="/grid_base.mako"/> \ No newline at end of file diff --git a/templates/admin/requests/manage_request_types.mako b/templates/admin/requests/manage_request_types.mako index 13192b61c58..8fc475526ee 100644 --- a/templates/admin/requests/manage_request_types.mako +++ b/templates/admin/requests/manage_request_types.mako @@ -1,68 +1 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -<%def name="title()">request Types - -%if msg: - ${render_msg( msg, messagetype )} -%endif - -

- Request Types -

- - - -
- ##Filter: - %for i, filter in enumerate( ['Active', 'Deleted', 'All'] ): - %if i > 0: - | - %endif - %if show_filter == filter: - ${filter} - %else: - ${filter} - %endif - %endfor -
-%if not request_types: - There are no request types. -%else: - - - - - - - - - - - %for request_type in request_types: - - - - - - - %endfor - -
NameDescriptionRequest FormSample Form
- ${request_type.name} - - %if request_type.deleted: -
- Undelete -
- %else: -
- Delete -
- %endif -
${request_type.desc}${request_type.request_form.name}${request_type.sample_form.name}
-%endif \ No newline at end of file +<%inherit file="/grid_base.mako"/> \ No newline at end of file diff --git a/templates/admin/requests/show_request.mako b/templates/admin/requests/show_request.mako index 5a4bf1c3353..aee34456561 100644 --- a/templates/admin/requests/show_request.mako +++ b/templates/admin/requests/show_request.mako @@ -127,7 +127,7 @@
diff --git a/templates/admin/requests/view_request_type.mako b/templates/admin/requests/view_request_type.mako index 0efb8912525..bc2e71506d8 100644 --- a/templates/admin/requests/view_request_type.mako +++ b/templates/admin/requests/view_request_type.mako @@ -7,43 +7,37 @@ %endif
-
View request type details
-
-
-
- - ${request_type.name} +
Request type information
+ +
+ + ${request_type.name} +
+
+
+ + ${request_type.desc} +
+
+
+ + ${request_type.request_form.name} +
+
+ + ${request_type.sample_form.name} +
+
Possible sample states
+ %for element_count, state in enumerate(states_list): +
+ + ${state.desc} +
-
-
- - ${request_type.desc} -
-
-
- - ${request_type.request_form.name} -
-
- - ${request_type.sample_form.name} -
-
- - %for element_count, state in enumerate(states_list): -
- - ${state.desc} -
-
- %endfor -
-
-
+ %endfor +
\ No newline at end of file diff --git a/templates/dataset/errors.mako b/templates/dataset/errors.mako index 11f79378675..3f4ef586ba1 100644 --- a/templates/dataset/errors.mako +++ b/templates/dataset/errors.mako @@ -18,10 +18,10 @@

Dataset generation errors

-

Dataset ${dataset.hid}: ${dataset.display_name()}

+

Dataset ${hda.hid}: ${hda.display_name()}

- %if dataset.creating_job_associations: - <% job = dataset.creating_job_associations[0].job %> + %if hda.creating_job_associations: + <% job = hda.creating_job_associations[0].job %> %if job.traceback: The Galaxy framework encountered the following error while attempting to run the tool:
${job.traceback}
@@ -43,7 +43,12 @@ %else: The tool did not create any additional job / error info. %endif - + <% + if trans.user: + user_email = trans.user.email + else: + user_email = '' + %>

Report this error to the Galaxy Team

The Galaxy team regularly reviews errors that occur in the application. @@ -56,10 +61,10 @@

Error Report
- +
- +
diff --git a/templates/grid_base.mako b/templates/grid_base.mako index 65df11e6743..5436415be80 100644 --- a/templates/grid_base.mako +++ b/templates/grid_base.mako @@ -347,8 +347,9 @@ ${self.grid_table()} %endif + ## Grid operations. + %if grid.operations: - ## Grid operations. For selected ${items_plural}: @@ -359,6 +360,7 @@ ${self.grid_table()} %endfor + %endif diff --git a/templates/grid_base_async.mako b/templates/grid_base_async.mako new file mode 100644 index 00000000000..8f936d1efc7 --- /dev/null +++ b/templates/grid_base_async.mako @@ -0,0 +1,698 @@ +<%! + from galaxy.web.framework.helpers.grids import TextColumn + from galaxy.model import History, HistoryDatasetAssociation, User, Role, Group + import galaxy.util + def inherit(context): + if context.get('use_panels'): + return '/base_panels.mako' + else: + return '/base.mako' +%> +<%inherit file="${inherit(context)}"/> + +## Render the grid's basic elements. Each of these elements can be subclassed. +%if message: +

+

${util.restore_text( message )}
+
+

+%endif + +${self.render_grid_header()} +${self.render_grid_table()} + +## Function definitions. + +<%def name="title()">${grid.title} + +<%def name="javascripts()"> + ${parent.javascripts()} + ${h.js("jquery.autocomplete", "autocomplete_tagging" )} + + + +<%def name="stylesheets()"> + ${h.css( "base", "autocomplete_tagging" )} + + + +<%namespace file="./grid_common_async.mako" import="*" /> + +## Print grid header. +<%def name="render_grid_header()"> +
+

${grid.title}

+ + %if grid.global_actions: + + %endif + + ${render_grid_filters()} +
+ + +## Print grid. +<%def name="render_grid_table()"> +
+ + + + + %for column in grid.columns: + %if column.visible: + <% + href = "" + extra = "" + if column.sortable: + if sort_key == column.key: + if sort_order == "asc": + href = url( sort=( "-" + column.key ) ) + extra = "↓" + else: + href = url( sort=( column.key ) ) + extra = "↑" + else: + href = url( sort=column.key ) + %> + 1: + colspan="${column.ncells}" + %endif + > + %if href: + ${column.label} + %else: + ${column.label} + %endif + ${extra} + + %endif + %endfor + + + + + ${render_grid_table_body_contents()} + + + ${render_grid_table_footer_contents()} + +
+
+ + +<%def name="render_grid_table_body_contents()"> + %if query.count() == 0: + ## No results. + No Items + %endif + %for i, item in enumerate( query ): + + ## Item selection column + + + + ## Data columns + %for column in grid.columns: + %if column.visible: + <% + # Link + link = column.get_link( trans, grid, item ) + if link: + href = url( **link ) + else: + href = None + # Value (coerced to list so we can loop) + value = column.get_value( trans, grid, item ) + if column.ncells == 1: + value = [ value ] + %> + %for cellnum, v in enumerate( value ): + <% + # Handle non-ascii chars. + if isinstance(v, str): + v = unicode(v, 'utf-8') + # Attach popup menu? + if column.attach_popup and cellnum == 0: + extra = '' % i + else: + extra = "" + %> + %if href: +