PageRenderTime 70ms CodeModel.GetById 13ms app.highlight 48ms RepoModel.GetById 2ms app.codeStats 0ms

/lib/galaxy/web/form_builder.py

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 775 lines | 768 code | 4 blank | 3 comment | 4 complexity | 6388948ac38b72286bd0ef4bd5f51926 MD5 | raw file
  1"""
  2Classes for generating HTML forms
  3"""
  4
  5import logging, sys, os, time
  6
  7from operator import itemgetter
  8from cgi import escape
  9from galaxy.util import restore_text, relpath, nice_size, unicodify
 10from galaxy.web import url_for
 11from binascii import hexlify
 12
 13log = logging.getLogger(__name__)
 14
 15class BaseField(object):
 16    def get_html( self, prefix="" ):
 17        """Returns the html widget corresponding to the parameter"""
 18        raise TypeError( "Abstract Method" )
 19    def get_disabled_str( self, disabled=False ):
 20        if disabled:
 21            return ' disabled="disabled"'
 22        else:
 23            return ''
 24
 25class TextField(BaseField):
 26    """
 27    A standard text input box.
 28
 29    >>> print TextField( "foo" ).get_html()
 30    <input type="text" name="foo" size="10" value="">
 31    >>> print TextField( "bins", size=4, value="default" ).get_html()
 32    <input type="text" name="bins" size="4" value="default">
 33    """
 34    def __init__( self, name, size=None, value=None ):
 35        self.name = name
 36        self.size = int( size or 10 )
 37        self.value = value or ""
 38    def get_html( self, prefix="", disabled=False ):
 39        value = self.value
 40        if not isinstance( value, basestring ):
 41            value = str( value )
 42        value = unicodify( value )
 43        return unicodify( '<input type="text" name="%s%s" size="%d" value="%s"%s>' \
 44            % ( prefix, self.name, self.size, escape( value,  quote=True ), self.get_disabled_str( disabled ) ) )
 45    def set_size(self, size):
 46        self.size = int( size )
 47
 48class PasswordField(BaseField):
 49    """
 50    A password input box. text appears as "******"
 51
 52    >>> print PasswordField( "foo" ).get_html()
 53    <input type="password" name="foo" size="10" value="">
 54    >>> print PasswordField( "bins", size=4, value="default" ).get_html()
 55    <input type="password" name="bins" size="4" value="default">
 56    """
 57    def __init__( self, name, size=None, value=None ):
 58        self.name = name
 59        self.size = int( size or 10 )
 60        self.value = value or ""
 61    def get_html( self, prefix="", disabled=False  ):
 62        return unicodify( '<input type="password" name="%s%s" size="%d" value="%s"%s>' \
 63            % ( prefix, self.name, self.size, escape( str( self.value ), quote=True ), self.get_disabled_str( disabled ) ) )
 64    def set_size(self, size):
 65        self.size = int( size )
 66
 67class TextArea(BaseField):
 68    """
 69    A standard text area box.
 70
 71    >>> print TextArea( "foo" ).get_html()
 72    <textarea name="foo" rows="5" cols="25"></textarea>
 73    >>> print TextArea( "bins", size="4x5", value="default" ).get_html()
 74    <textarea name="bins" rows="4" cols="5">default</textarea>
 75    """
 76    _DEFAULT_SIZE = "5x25"
 77    def __init__( self, name, size=None, value=None ):
 78        self.name = name
 79        size = size or self._DEFAULT_SIZE
 80        self.size = size.split("x")
 81        self.rows = int(self.size[0])
 82        self.cols = int(self.size[-1])
 83        self.value = value or ""
 84    def get_html( self, prefix="", disabled=False ):
 85        return unicodify( '<textarea name="%s%s" rows="%d" cols="%d"%s>%s</textarea>' \
 86            % ( prefix, self.name, self.rows, self.cols, self.get_disabled_str( disabled ), escape( str( self.value ), quote=True ) ) )
 87    def set_size(self, rows, cols):
 88        self.rows = rows
 89        self.cols = cols
 90
 91class CheckboxField(BaseField):
 92    """
 93    A checkbox (boolean input)
 94
 95    >>> print CheckboxField( "foo" ).get_html()
 96    <input type="checkbox" id="foo" name="foo" value="true"><input type="hidden" name="foo" value="true">
 97    >>> print CheckboxField( "bar", checked="yes" ).get_html()
 98    <input type="checkbox" id="bar" name="bar" value="true" checked="checked"><input type="hidden" name="bar" value="true">
 99    """
100    def __init__( self, name, checked=None, refresh_on_change = False, refresh_on_change_values = None ):
101        self.name = name
102        self.checked = ( checked == True ) or ( isinstance( checked, basestring ) and ( checked.lower() in ( "yes", "true", "on" ) ) )
103        self.refresh_on_change = refresh_on_change
104        self.refresh_on_change_values = refresh_on_change_values or []
105        if self.refresh_on_change:
106            self.refresh_on_change_text = ' refresh_on_change="true" '
107            if self.refresh_on_change_values:
108                self.refresh_on_change_text = '%s refresh_on_change_values="%s" ' % ( self.refresh_on_change_text, ",".join( self.refresh_on_change_values ) )
109        else:
110            self.refresh_on_change_text = ''
111    def get_html( self, prefix="", disabled=False ):
112        if self.checked:
113            checked_text = ' checked="checked"'
114        else:
115            checked_text = ""
116        id_name = prefix + self.name
117        # The hidden field is necessary because if the check box is not checked on the form, it will
118        # not be included in the request params.  The hidden field ensure that this will happen.  When
119        # parsing the request, the value 'true' in the hidden field actually means it is NOT checked.
120        # See the is_checked() method below.  The prefix is necessary in each case to ensure functional
121        # correctness when the param is inside a conditional.
122        return unicodify( '<input type="checkbox" id="%s" name="%s" value="true"%s%s%s><input type="hidden" name="%s%s" value="true"%s>' \
123            % ( id_name, id_name, checked_text, self.get_disabled_str( disabled ), self.refresh_on_change_text, prefix, self.name, self.get_disabled_str( disabled ) ) )
124    @staticmethod
125    def is_checked( value ):
126        if value == True:
127            return True
128        # This may look strange upon initial inspection, but see the comments in the get_html() method
129        # above for clarification.  Basically, if value is not True, then it will always be a list with
130        # 2 input fields ( a checkbox and a hidden field ) if the checkbox is checked.  If it is not
131        # checked, then value will be only the hidden field.
132        return isinstance( value, list ) and len( value ) == 2
133    def set_checked(self, value):
134        if isinstance( value, basestring ):
135            self.checked = value.lower() in [ "yes", "true", "on" ]
136        else:
137            self.checked = value
138
139class FileField(BaseField):
140    """
141    A file upload input.
142
143    >>> print FileField( "foo" ).get_html()
144    <input type="file" name="foo">
145    >>> print FileField( "foo", ajax = True ).get_html()
146    <input type="file" name="foo" galaxy-ajax-upload="true">
147    """
148    def __init__( self, name, value = None, ajax=False ):
149        self.name = name
150        self.ajax = ajax
151        self.value = value
152    def get_html( self, prefix="" ):
153        value_text = ""
154        if self.value:
155            value_text = ' value="%s"' % escape( str( self.value ),  quote=True )
156        ajax_text = ""
157        if self.ajax:
158            ajax_text = ' galaxy-ajax-upload="true"'
159        return unicodify( '<input type="file" name="%s%s"%s%s>' % ( prefix, self.name, ajax_text, value_text ) )
160
161class FTPFileField(BaseField):
162    """
163    An FTP file upload input.
164    """
165    thead = '''
166        <table id="grid-table" class="grid">
167            <thead id="grid-table-header">
168                <tr>
169                    <th id="select-header"></th>
170                    <th id="name-header">
171                        File
172                    </th>
173                    <th id="size-header">
174                        Size
175                    </th>
176                    <th id="date-header">
177                        Date
178                    </th>
179                </tr>
180            </thead>
181            <tbody id="grid-table-body">
182    '''
183    trow = '''
184                <tr>
185                    <td><input type="checkbox" name="%s%s" value="%s"/></td>
186                    <td>%s</td>
187                    <td>%s</td>
188                    <td>%s</td>
189                </tr>
190    '''
191    tfoot = '''
192            </tbody>
193        </table>
194    '''
195    def __init__( self, name, dir, ftp_site, value = None ):
196        self.name = name
197        self.dir = dir
198        self.ftp_site = ftp_site
199        self.value = value
200    def get_html( self, prefix="" ):
201        rval = FTPFileField.thead
202        if self.dir is None:
203            rval += '<tr><td colspan="4"><em>Please <a href="%s">create</a> or <a href="%s">log in to</a> a Galaxy account to view files uploaded via ASPERA.</em></td></tr>' % ( url_for( controller='user', action='create', cntrller='user', referer=url_for( controller='root' ) ), url_for( controller='user', action='login', cntrller='user', referer=url_for( controller='root' ) ) )
204        elif not os.path.exists( self.dir ):
205            rval += '<tr><td colspan="4"><em>Your ASPERA upload directory contains no files.</em></td></tr>'
206        else:
207            uploads = []
208            for ( dirpath, dirnames, filenames ) in os.walk( self.dir ):
209                for filename in filenames:
210                    path = relpath( os.path.join( dirpath, filename ), self.dir )
211                    statinfo = os.lstat( os.path.join( dirpath, filename ) )
212                    uploads.append( dict( path=path,
213                                          size=nice_size( statinfo.st_size ),
214                                          ctime=time.strftime( "%m/%d/%Y %I:%M:%S %p", time.localtime( statinfo.st_ctime ) ) ) )
215            if not uploads:
216                rval += '<tr><td colspan="4"><em>Your ASPERA upload directory contains no files.</em></td></tr>'
217            uploads = sorted(uploads, key=itemgetter("path"))
218            for upload in uploads:
219                rval += FTPFileField.trow % ( prefix, self.name, upload['path'], upload['path'], upload['size'], upload['ctime'] )
220        rval += FTPFileField.tfoot
221        rval += '<div class="toolParamHelp">This Galaxy server allows you to upload files via ASPERA.  To upload some files, log in to the ASPERA server at <strong>%s</strong> using your Cistrome credentials (email address and password).</div>' % self.ftp_site
222        return rval
223
224class HiddenField(BaseField):
225    """
226    A hidden field.
227
228    >>> print HiddenField( "foo", 100 ).get_html()
229    <input type="hidden" name="foo" value="100">
230    """
231    def __init__( self, name, value=None ):
232        self.name = name
233        self.value = value or ""
234    def get_html( self, prefix="" ):
235        return unicodify( '<input type="hidden" name="%s%s" value="%s">' % ( prefix, self.name, escape( str( self.value ), quote=True ) ) )
236
237class SelectField(BaseField):
238    """
239    A select field.
240
241    >>> t = SelectField( "foo", multiple=True )
242    >>> t.add_option( "tuti", 1 )
243    >>> t.add_option( "fruity", "x" )
244    >>> print t.get_html()
245    <select name="foo" multiple>
246    <option value="1">tuti</option>
247    <option value="x">fruity</option>
248    </select>
249
250    >>> t = SelectField( "bar" )
251    >>> t.add_option( "automatic", 3 )
252    >>> t.add_option( "bazooty", 4, selected=True )
253    >>> print t.get_html()
254    <select name="bar" last_selected_value="4">
255    <option value="3">automatic</option>
256    <option value="4" selected>bazooty</option>
257    </select>
258
259    >>> t = SelectField( "foo", display="radio" )
260    >>> t.add_option( "tuti", 1 )
261    >>> t.add_option( "fruity", "x" )
262    >>> print t.get_html()
263    <div><input type="radio" name="foo" value="1" id="foo|1"><label class="inline" for="foo|1">tuti</label></div>
264    <div><input type="radio" name="foo" value="x" id="foo|x"><label class="inline" for="foo|x">fruity</label></div>
265
266    >>> t = SelectField( "bar", multiple=True, display="checkboxes" )
267    >>> t.add_option( "automatic", 3 )
268    >>> t.add_option( "bazooty", 4, selected=True )
269    >>> print t.get_html()
270    <div class="checkUncheckAllPlaceholder" checkbox_name="bar"></div>
271    <div><input type="checkbox" name="bar" value="3" id="bar|3"><label class="inline" for="bar|3">automatic</label></div>
272    <div><input type="checkbox" name="bar" value="4" id="bar|4" checked='checked'><label class="inline" for="bar|4">bazooty</label></div>
273    """
274    def __init__( self, name, multiple=None, display=None, refresh_on_change=False, refresh_on_change_values=None, size=None ):
275        self.name = name
276        self.multiple = multiple or False
277        self.size = size
278        self.options = list()
279        if display == "checkboxes":
280            assert multiple, "Checkbox display only supported for multiple select"
281        elif display == "radio":
282            assert not( multiple ), "Radio display only supported for single select"
283        elif display is not None:
284            raise Exception, "Unknown display type: %s" % display
285        self.display = display
286        self.refresh_on_change = refresh_on_change
287        self.refresh_on_change_values = refresh_on_change_values or []
288        if self.refresh_on_change:
289            self.refresh_on_change_text = ' refresh_on_change="true"'
290            if self.refresh_on_change_values:
291                self.refresh_on_change_text = '%s refresh_on_change_values="%s"' % ( self.refresh_on_change_text, escape( ",".join( self.refresh_on_change_values ), quote=True ) )
292        else:
293            self.refresh_on_change_text = ''
294    def add_option( self, text, value, selected = False ):
295        self.options.append( ( text, value, selected ) )
296    def get_html( self, prefix="", disabled=False ):
297        if self.display == "checkboxes":
298            return self.get_html_checkboxes( prefix, disabled )
299        elif self.display == "radio":
300            return self.get_html_radio( prefix, disabled )
301        else:
302            return self.get_html_default( prefix, disabled )
303    def get_html_checkboxes( self, prefix="", disabled=False ):
304        rval = []
305        ctr = 0
306        if len( self.options ) > 1:
307            rval.append ( '<div class="checkUncheckAllPlaceholder" checkbox_name="%s%s"></div>' % ( prefix, self.name ) ) #placeholder for the insertion of the Select All/Unselect All buttons
308        for text, value, selected in self.options:
309            style = ""
310            if not isinstance( value, basestring ):
311                value = str( value )
312            if not isinstance( text, basestring ):
313                text = str( text )
314            text = unicodify( text )
315            escaped_value = escape( unicodify( value ), quote=True )
316            uniq_id = "%s%s|%s" % (prefix, self.name, escaped_value)
317            if len(self.options) > 2 and ctr % 2 == 1:
318                style = " class=\"odd_row\""
319            selected_text = ""
320            if selected:
321                selected_text = " checked='checked'"
322            rval.append( '<div%s><input type="checkbox" name="%s%s" value="%s" id="%s"%s%s><label class="inline" for="%s">%s</label></div>' % \
323                ( style, prefix, self.name, escaped_value, uniq_id, selected_text, self.get_disabled_str( disabled ), uniq_id, escape( text, quote=True ) ) )
324            ctr += 1
325        return unicodify( "\n".join( rval ) )
326    def get_html_radio( self, prefix="", disabled=False ):
327        rval = []
328        ctr = 0
329        for text, value, selected in self.options:
330            style = ""
331            escaped_value = escape( str( value ), quote=True )
332            uniq_id = "%s%s|%s" % (prefix, self.name, escaped_value)
333            if len(self.options) > 2 and ctr % 2 == 1:
334                style = " class=\"odd_row\""
335            selected_text = ""
336            if selected:
337                selected_text = " checked='checked'"
338            rval.append( '<div%s><input type="radio" name="%s%s"%s value="%s" id="%s"%s%s><label class="inline" for="%s">%s</label></div>' % \
339                         ( style,
340                           prefix,
341                           self.name,
342                           self.refresh_on_change_text,
343                           escaped_value,
344                           uniq_id,
345                           selected_text,
346                           self.get_disabled_str( disabled ),
347                           uniq_id,
348                           text ) )
349            ctr += 1
350        return unicodify( "\n".join( rval ) )
351    def get_html_default( self, prefix="", disabled=False ):
352        if self.multiple:
353            multiple = " multiple"
354        else:
355            multiple = ""
356        if self.size:
357            size = ' size="%s"' % str( self.size )
358        else:
359            size = ''
360        rval = []
361        last_selected_value = ""
362        for text, value, selected in self.options:
363            if selected:
364                selected_text = " selected"
365                last_selected_value = value
366                if not isinstance( last_selected_value, basestring ):
367                    last_selected_value = str( last_selected_value )
368            else:
369                selected_text = ""
370            if not isinstance( value, basestring ):
371                value = str( value )
372            if not isinstance( text, basestring ):
373                text = str( text )
374            rval.append( '<option value="%s"%s>%s</option>' % ( escape( unicodify( value ), quote=True ), selected_text, escape( unicodify( text ), quote=True ) ) )
375        if last_selected_value:
376            last_selected_value = ' last_selected_value="%s"' % escape( unicodify( last_selected_value ), quote=True )
377        rval.insert( 0, '<select name="%s%s"%s%s%s%s%s>' % \
378                     ( prefix, self.name, multiple, size, self.refresh_on_change_text, last_selected_value, self.get_disabled_str( disabled ) ) )
379        rval.append( '</select>' )
380        return unicodify( "\n".join( rval ) )
381    def get_selected( self, return_label=False, return_value=False, multi=False ):
382        '''
383        Return the currently selected option's label, value or both as a tuple.  For
384        multi-select lists, a list is returned.
385        '''
386        if multi:
387            selected_options = []
388        for label, value, selected in self.options:
389            if selected:
390                if return_label and return_value:
391                    if multi:
392                        selected_options.append( ( label, value ) )
393                    else:
394                        return ( label, value )
395                elif return_label:
396                    if multi:
397                        selected_options.append( label )
398                    else:
399                        return label
400                elif return_value:
401                    if multi:
402                        selected_options.append( value )
403                    else:
404                        return value
405        if multi:
406            return selected_options
407        return None
408
409class DrillDownField( BaseField ):
410    """
411    A hierarchical select field, which allows users to 'drill down' a tree-like set of options.
412
413    >>> t = DrillDownField( "foo", multiple=True, display="checkbox", options=[{'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 1', 'value': 'option1', 'options': []}, {'name': 'Option 2', 'value': 'option2', 'options': []}, {'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 3', 'value': 'option3', 'options': []}, {'name': 'Option 4', 'value': 'option4', 'options': []}]}]}, {'name': 'Option 5', 'value': 'option5', 'options': []}] )
414    >>> print t.get_html()
415    <div class="form-row drilldown-container" id="drilldown--666f6f">
416    <div class="form-row-input">
417    <div><span class="form-toggle icon-button toggle-expand" id="drilldown--666f6f-68656164696e6731-click"></span>
418    <input type="checkbox" name="foo" value="heading1" >Heading 1
419    </div><div class="form-row" id="drilldown--666f6f-68656164696e6731-container" style="float: left; margin-left: 1em;">
420    <div class="form-row-input">
421    <input type="checkbox" name="foo" value="option1" >Option 1
422    </div>
423    <div class="form-row-input">
424    <input type="checkbox" name="foo" value="option2" >Option 2
425    </div>
426    <div class="form-row-input">
427    <div><span class="form-toggle icon-button toggle-expand" id="drilldown--666f6f-68656164696e6731-68656164696e6731-click"></span>
428    <input type="checkbox" name="foo" value="heading1" >Heading 1
429    </div><div class="form-row" id="drilldown--666f6f-68656164696e6731-68656164696e6731-container" style="float: left; margin-left: 1em;">
430    <div class="form-row-input">
431    <input type="checkbox" name="foo" value="option3" >Option 3
432    </div>
433    <div class="form-row-input">
434    <input type="checkbox" name="foo" value="option4" >Option 4
435    </div>
436    </div>
437    </div>
438    </div>
439    </div>
440    <div class="form-row-input">
441    <input type="checkbox" name="foo" value="option5" >Option 5
442    </div>
443    </div>
444    >>> t = DrillDownField( "foo", multiple=False, display="radio", options=[{'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 1', 'value': 'option1', 'options': []}, {'name': 'Option 2', 'value': 'option2', 'options': []}, {'name': 'Heading 1', 'value': 'heading1', 'options': [{'name': 'Option 3', 'value': 'option3', 'options': []}, {'name': 'Option 4', 'value': 'option4', 'options': []}]}]}, {'name': 'Option 5', 'value': 'option5', 'options': []}] )
445    >>> print t.get_html()
446    <div class="form-row drilldown-container" id="drilldown--666f6f">
447    <div class="form-row-input">
448    <div><span class="form-toggle icon-button toggle-expand" id="drilldown--666f6f-68656164696e6731-click"></span>
449    <input type="radio" name="foo" value="heading1" >Heading 1
450    </div><div class="form-row" id="drilldown--666f6f-68656164696e6731-container" style="float: left; margin-left: 1em;">
451    <div class="form-row-input">
452    <input type="radio" name="foo" value="option1" >Option 1
453    </div>
454    <div class="form-row-input">
455    <input type="radio" name="foo" value="option2" >Option 2
456    </div>
457    <div class="form-row-input">
458    <div><span class="form-toggle icon-button toggle-expand" id="drilldown--666f6f-68656164696e6731-68656164696e6731-click"></span>
459    <input type="radio" name="foo" value="heading1" >Heading 1
460    </div><div class="form-row" id="drilldown--666f6f-68656164696e6731-68656164696e6731-container" style="float: left; margin-left: 1em;">
461    <div class="form-row-input">
462    <input type="radio" name="foo" value="option3" >Option 3
463    </div>
464    <div class="form-row-input">
465    <input type="radio" name="foo" value="option4" >Option 4
466    </div>
467    </div>
468    </div>
469    </div>
470    </div>
471    <div class="form-row-input">
472    <input type="radio" name="foo" value="option5" >Option 5
473    </div>
474    </div>
475    """
476    def __init__( self, name, multiple=None, display=None, refresh_on_change=False, options = [], value = [], refresh_on_change_values = [] ):
477        self.name = name
478        self.multiple = multiple or False
479        self.options = options
480        if value and not isinstance( value, list ):
481            value = [ value ]
482        elif not value:
483            value = []
484        self.value = value
485        if display == "checkbox":
486            assert multiple, "Checkbox display only supported for multiple select"
487        elif display == "radio":
488            assert not( multiple ), "Radio display only supported for single select"
489        else:
490            raise Exception, "Unknown display type: %s" % display
491        self.display = display
492        self.refresh_on_change = refresh_on_change
493        self.refresh_on_change_values = refresh_on_change_values
494        if self.refresh_on_change:
495            self.refresh_on_change_text = ' refresh_on_change="true"'
496            if self.refresh_on_change_values:
497                self.refresh_on_change_text = '%s refresh_on_change_values="%s"' % ( self.refresh_on_change_text, ",".join( self.refresh_on_change_values ) )
498        else:
499            self.refresh_on_change_text = ''
500    def get_html( self, prefix="" ):
501        def find_expanded_options( expanded_options, options, parent_options = [] ):
502            for option in options:
503                if option['value'] in self.value:
504                    expanded_options.extend( parent_options )
505                if option['options']:
506                    new_parents = list( parent_options ) + [ option['value'] ]
507                    find_expanded_options( expanded_options, option['options'], new_parents )
508        def recurse_options( html, options, base_id, expanded_options = [] ):
509            for option in options:
510                escaped_option_value = escape( str( option['value'] ), quote=True )
511                selected = ( option['value'] in self.value )
512                if selected:
513                    selected = ' checked'
514                else:
515                    selected = ''
516                span_class = 'form-toggle icon-button toggle'
517                if option['value'] not in expanded_options:
518                    span_class = "%s-expand" % ( span_class )
519                html.append( '<div class="form-row-input">')
520                drilldown_group_id = "%s-%s" % ( base_id, hexlify( option['value'] ) )
521                if option['options']:
522                    html.append( '<div><span class="%s" id="%s-click"></span>' % ( span_class, drilldown_group_id ) )
523                html.append( '<input type="%s" name="%s%s" value="%s" %s>%s' % ( self.display, prefix, self.name, escaped_option_value, selected, option['name']) )
524                if option['options']:
525                    html.append( '</div><div class="form-row" id="%s-container" style="float: left; margin-left: 1em;">' % ( drilldown_group_id )  )
526                    recurse_options( html, option['options'], drilldown_group_id, expanded_options )
527                    html.append( '</div>')
528                html.append( '</div>')
529        drilldown_id = "drilldown-%s-%s" % ( hexlify( prefix ), hexlify( self.name ) )
530        rval = []
531        rval.append( '<div class="form-row drilldown-container" id="%s">' % ( drilldown_id ) )
532        expanded_options = []
533        find_expanded_options( expanded_options, self.options )
534        recurse_options( rval, self.options, drilldown_id, expanded_options )
535        rval.append( '</div>' )
536        return unicodify( '\n'.join( rval ) )
537
538class AddressField(BaseField):
539    @staticmethod
540    def fields():
541        return   [  ( "short_desc", "Short address description", "Required" ),
542                    ( "name", "Name", "Required" ),
543                    ( "institution", "Institution", "Required" ),
544                    ( "address", "Address", "Required" ),
545                    ( "city", "City", "Required" ),
546                    ( "state", "State/Province/Region", "Required" ),
547                    ( "postal_code", "Postal Code", "Required" ),
548                    ( "country", "Country", "Required" ),
549                    ( "phone", "Phone", "" )  ]
550    def __init__(self, name, user=None, value=None, params=None):
551        self.name = name
552        self.user = user
553        self.value = value
554        self.select_address = None
555        self.params = params
556    def get_html( self, disabled=False ):
557        address_html = ''
558        add_ids = ['none']
559        if self.user:
560            for a in self.user.addresses:
561                add_ids.append( str( a.id ) )
562        add_ids.append( 'new' )
563        self.select_address = SelectField( self.name,
564                                           refresh_on_change=True,
565                                           refresh_on_change_values=add_ids )
566        if self.value == 'none':
567            self.select_address.add_option( 'Select one', 'none', selected=True )
568        else:
569            self.select_address.add_option( 'Select one', 'none' )
570        if self.user:
571            for a in self.user.addresses:
572                if not a.deleted:
573                    if self.value == str( a.id ):
574                        self.select_address.add_option( a.desc, str( a.id ), selected=True )
575                        # Display this address
576                        address_html += '''
577                                        <div class="form-row">
578                                            %s
579                                        </div>
580                                        ''' % a.get_html()
581                    else:
582                        self.select_address.add_option( a.desc, str( a.id ) )
583        if self.value == 'new':
584            self.select_address.add_option( 'Add a new address', 'new', selected=True )
585            for field_name, label, help_text in self.fields():
586                add_field = TextField( self.name + '_' + field_name,
587                                      40,
588                                      restore_text( self.params.get( self.name + '_' + field_name, ''  ) ) )
589                address_html += '''
590                                <div class="form-row">
591                                    <label>%s</label>
592                                    %s
593                                    ''' % ( label, add_field.get_html( disabled=disabled ) )
594                if help_text:
595                    address_html += '''
596                                    <div class="toolParamHelp" style="clear: both;">
597                                        %s
598                                    </div>
599                                    ''' % help_text
600                address_html += '''
601                                </div>
602                                '''
603        else:
604            self.select_address.add_option( 'Add a new address', 'new' )
605        return self.select_address.get_html( disabled=disabled ) + address_html
606
607class WorkflowField( BaseField ):
608    def __init__( self, name, user=None, value=None, params=None ):
609        self.name = name
610        self.user = user
611        self.value = value
612        self.select_workflow = None
613        self.params = params
614    def get_html( self, disabled=False ):
615        self.select_workflow = SelectField( self.name )
616        if self.value == 'none':
617            self.select_workflow.add_option( 'Select one', 'none', selected=True )
618        else:
619            self.select_workflow.add_option( 'Select one', 'none' )
620        if self.user:
621            for a in self.user.stored_workflows:
622                if not a.deleted:
623                    if str( self.value ) == str( a.id ):
624                        self.select_workflow.add_option( a.name, str( a.id ), selected=True )
625                    else:
626                        self.select_workflow.add_option( a.name, str( a.id ) )
627        return self.select_workflow.get_html( disabled=disabled )
628
629class WorkflowMappingField( BaseField):
630    def __init__( self, name, user=None, value=None, params=None, **kwd ):
631        # DBTODO integrate this with the new __build_workflow approach in requests_common.  As it is, not particularly useful.
632        self.name = name
633        self.user = user
634        self.value = value
635        self.select_workflow = None
636        self.params = params
637        self.workflow_inputs = []
638    def get_html( self, disabled=False ):
639        self.select_workflow = SelectField( self.name, refresh_on_change = True )
640        workflow_inputs = []
641        if self.value == 'none':
642            self.select_workflow.add_option( 'Select one', 'none', selected=True )
643        else:
644            self.select_workflow.add_option( 'Select one', 'none' )
645        if self.user:
646            for a in self.user.stored_workflows:
647                if not a.deleted:
648                    if str( self.value ) == str( a.id ):
649                        self.select_workflow.add_option( a.name, str( a.id ), selected=True )
650                    else:
651                        self.select_workflow.add_option( a.name, str( a.id ) )
652            if self.value and self.value != 'none':
653                # Workflow selected.  Find all inputs.
654                for workflow in self.user.stored_workflows:
655                    if workflow.id == int(self.value):
656                        for step in workflow.latest_workflow.steps:
657                            if step.type == 'data_input':
658                                if step.tool_inputs and "name" in step.tool_inputs:
659                                    workflow_inputs.append((step.tool_inputs['name'], TextField( '%s_%s' % (self.name, step.id), 20)))
660        # Do something more appropriate here and allow selection of inputs
661        return self.select_workflow.get_html( disabled=disabled ) + ''.join(['<div class="form-row"><label>%s</label>%s</div>' % (s[0], s[1].get_html()) for s in workflow_inputs])
662    def get_display_text(self):
663        if self.value:
664            return self.value
665        else:
666            return '-'
667class HistoryField( BaseField ):
668    def __init__( self, name, user=None, value=None, params=None ):
669        self.name = name
670        self.user = user
671        self.value = value
672        self.select_history = None
673        self.params = params
674    def get_html( self, disabled=False ):
675        self.select_history = SelectField( self.name )
676        if self.value == 'none':
677            self.select_history.add_option( 'No Import', 'none', selected=True )
678            self.select_history.add_option( 'New History', 'new' )
679        else:
680            self.select_history.add_option( 'No Import', 'none' )
681            if self.value == 'new':
682                self.select_history.add_option( 'New History', 'new', selected=True )
683            else:
684                self.select_history.add_option( 'New History', 'new')
685        if self.user:
686            for a in self.user.histories:
687                if not a.deleted:
688                    if str( self.value ) == str( a.id ):
689                        self.select_history.add_option( a.name, str( a.id ), selected=True )
690                    else:
691                        self.select_history.add_option( a.name, str( a.id ) )
692        return self.select_history.get_html( disabled=disabled )
693    def get_display_text(self):
694        if self.value:
695            return self.value
696        else:
697            return '-'
698
699class LibraryField( BaseField ):
700    def __init__( self, name, value=None, trans=None ):
701        self.name = name
702        self.lddas = value
703        self.trans = trans
704    def get_html( self, prefix="", disabled=False ):
705        if not self.lddas:
706            ldda_ids = ""
707            text = "Select library dataset(s)"
708        else:
709            ldda_ids = "||".join( [ self.trans.security.encode_id( ldda.id ) for ldda in self.lddas ] )
710            text = "<br />".join( [ "%s. %s" % (i+1, ldda.name) for i, ldda in enumerate(self.lddas)] )
711        return unicodify( '<a href="javascript:void(0);" class="add-librarydataset">%s</a> \
712                <input type="hidden" name="%s%s" value="%s">' % ( text, prefix, self.name, escape( str(ldda_ids), quote=True ) ) )
713
714    def get_display_text(self):
715        if self.ldda:
716            return self.ldda.name
717        else:
718            return 'None'
719
720def get_suite():
721    """Get unittest suite for this module"""
722    import doctest, sys
723    return doctest.DocTestSuite( sys.modules[__name__] )
724
725# --------- Utility methods -----------------------------
726
727def build_select_field( trans, objs, label_attr,  select_field_name, initial_value='none',
728                        selected_value='none', refresh_on_change=False, multiple=False, display=None, size=None ):
729    """
730    Build a SelectField given a set of objects.  The received params are:
731
732    - objs: the set of objects used to populate the option list
733    - label_attr: the attribute of each obj (e.g., name, email, etc ) whose value is used to populate each option label.
734
735        - If the string 'self' is passed as label_attr, each obj in objs is assumed to be a string, so the obj itself is used
736
737    - select_field_name: the name of the SelectField
738    - initial_value: the value of the first option in the SelectField - allows for an option telling the user to select something
739    - selected_value: the value of the currently selected option
740    - refresh_on_change: True if the SelectField should perform a refresh_on_change
741    """
742    if initial_value == 'none':
743        values = [ initial_value ]
744    else:
745        values = []
746    for obj in objs:
747        if label_attr == 'self':
748            # Each obj is a string
749            values.append( obj )
750        else:
751            values.append( trans.security.encode_id( obj.id ) )
752    if refresh_on_change:
753        refresh_on_change_values = values
754    else:
755        refresh_on_change_values = []
756    select_field = SelectField( name=select_field_name,
757                                multiple=multiple,
758                                display=display,
759                                refresh_on_change=refresh_on_change,
760                                refresh_on_change_values=refresh_on_change_values,
761                                size=size )
762    for obj in objs:
763        if label_attr == 'self':
764            # Each obj is a string
765            if str( selected_value ) == str( obj ):
766                select_field.add_option( obj, obj, selected=True )
767            else:
768                select_field.add_option( obj, obj )
769        else:
770            label = getattr( obj, label_attr )
771            if str( selected_value ) == str( obj.id ) or str( selected_value ) == trans.security.encode_id( obj.id ):
772                select_field.add_option( label, trans.security.encode_id( obj.id ), selected=True )
773            else:
774                select_field.add_option( label, trans.security.encode_id( obj.id ) )
775    return select_field