/lib/galaxy/web/framework/__init__.py
Python | 692 lines | 589 code | 14 blank | 89 comment | 52 complexity | a50ed1323a738185bd6f52a268742a61 MD5 | raw file
1"""
2Galaxy web application framework
3"""
4
5import pkg_resources
6
7import os, sys, time, socket, random, string
8pkg_resources.require( "Cheetah" )
9from Cheetah.Template import Template
10import base
11import pickle
12from galaxy import util
13
14pkg_resources.require( "simplejson" )
15import simplejson
16
17import helpers
18
19pkg_resources.require( "PasteDeploy" )
20from paste.deploy.converters import asbool
21
22pkg_resources.require( "Mako" )
23import mako.template
24import mako.lookup
25import mako.runtime
26
27pkg_resources.require( "Babel" )
28from babel.support import Translations
29
30pkg_resources.require( "SQLAlchemy >= 0.4" )
31from sqlalchemy import and_
32
33import logging
34log = logging.getLogger( __name__ )
35
36url_for = base.routes.url_for
37
38UCSC_SERVERS = (
39 'hgw1.cse.ucsc.edu',
40 'hgw2.cse.ucsc.edu',
41 'hgw3.cse.ucsc.edu',
42 'hgw4.cse.ucsc.edu',
43 'hgw5.cse.ucsc.edu',
44 'hgw6.cse.ucsc.edu',
45 'hgw7.cse.ucsc.edu',
46 'hgw8.cse.ucsc.edu',
47)
48
49def expose( func ):
50 """
51 Decorator: mark a function as 'exposed' and thus web accessible
52 """
53 func.exposed = True
54 return func
55
56def json( func ):
57 def decorator( self, trans, *args, **kwargs ):
58 trans.response.set_content_type( "text/javascript" )
59 return simplejson.dumps( func( self, trans, *args, **kwargs ) )
60 if not hasattr(func, '_orig'):
61 decorator._orig = func
62 decorator.exposed = True
63 return decorator
64
65def require_login( verb="perform this action" ):
66 def argcatcher( func ):
67 def decorator( self, trans, *args, **kwargs ):
68 if trans.get_user():
69 return func( self, trans, *args, **kwargs )
70 else:
71 return trans.show_error_message(
72 "You must be <a target='galaxy_main' href='%s'>logged in</a> to %s</div>"
73 % ( url_for( controller='user', action='login' ), verb ) )
74 return decorator
75 return argcatcher
76
77def require_admin( func ):
78 def decorator( self, trans, *args, **kwargs ):
79 admin_users = trans.app.config.get( "admin_users", "" ).split( "," )
80 if not admin_users:
81 return trans.show_error_message( "You must be logged in as an administrator to access this feature, but no administrators are set in the Galaxy configuration." )
82 user = trans.get_user()
83 if not user:
84 return trans.show_error_message( "You must be logged in as an administrator to access this feature." )
85 if not user.email in admin_users:
86 return trans.show_error_message( "You must be an administrator to access this feature." )
87 return func( self, trans, *args, **kwargs )
88 return decorator
89
90NOT_SET = object()
91
92class MessageException( Exception ):
93 """
94 Exception to make throwing errors from deep in controllers easier
95 """
96 def __init__( self, err_msg, type="info" ):
97 self.err_msg = err_msg
98 self.type = type
99
100def error( message ):
101 raise MessageException( message, type='error' )
102
103def form( *args, **kwargs ):
104 return FormBuilder( *args, **kwargs )
105
106class WebApplication( base.WebApplication ):
107 def __init__( self, galaxy_app, session_cookie='galaxysession' ):
108 base.WebApplication.__init__( self )
109 self.set_transaction_factory( lambda e: UniverseWebTransaction( e, galaxy_app, self, session_cookie ) )
110 # Mako support
111 self.mako_template_lookup = mako.lookup.TemplateLookup(
112 directories = [ galaxy_app.config.template_path ] ,
113 module_directory = galaxy_app.config.template_cache,
114 collection_size = 500,
115 output_encoding = 'utf-8' )
116 # Security helper
117 self.security = galaxy_app.security
118 def handle_controller_exception( self, e, trans, **kwargs ):
119 if isinstance( e, MessageException ):
120 return trans.show_message( e.err_msg, e.type )
121 def make_body_iterable( self, trans, body ):
122 if isinstance( body, FormBuilder ):
123 body = trans.show_form( body )
124 return base.WebApplication.make_body_iterable( self, trans, body )
125
126class UniverseWebTransaction( base.DefaultWebTransaction ):
127 """
128 Encapsulates web transaction specific state for the Universe application
129 (specifically the user's "cookie" session and history)
130 """
131 def __init__( self, environ, app, webapp, session_cookie ):
132 self.app = app
133 self.webapp = webapp
134 self.security = webapp.security
135 # FIXME: the following 3 attributes are not currently used
136 # Remove them if they are not going to be...
137 self.__user = NOT_SET
138 self.__history = NOT_SET
139 self.__galaxy_session = NOT_SET
140 base.DefaultWebTransaction.__init__( self, environ )
141 self.setup_i18n()
142 self.sa_session.clear()
143 self.debug = asbool( self.app.config.get( 'debug', False ) )
144 # Flag indicating whether we are in workflow building mode (means
145 # that the current history should not be used for parameter values
146 # and such).
147 self.workflow_building_mode = False
148 # Always have a valid galaxy session
149 self.__ensure_valid_session( session_cookie )
150 # Prevent deleted users from accessing Galaxy
151 if self.app.config.use_remote_user and self.galaxy_session.user.deleted:
152 self.response.send_redirect( url_for( '/static/user_disabled.html' ) )
153 if self.app.config.require_login:
154 self.__ensure_logged_in_user( environ )
155 def setup_i18n( self ):
156 if 'HTTP_ACCEPT_LANGUAGE' in self.environ:
157 # locales looks something like: ['en', 'en-us;q=0.7', 'ja;q=0.3']
158 locales = self.environ['HTTP_ACCEPT_LANGUAGE'].split( ',' )
159 locales = [ l.split( ';' )[0] for l in locales ]
160 else:
161 # Default to English
162 locales = 'en'
163 t = Translations.load( dirname='locale', locales=locales, domain='ginga' )
164 self.template_context.update ( dict( _=t.ugettext, n_=t.ugettext, N_=t.ungettext ) )
165 @property
166 def sa_session( self ):
167 """
168 Returns a SQLAlchemy session -- currently just gets the current
169 session from the threadlocal session context, but this is provided
170 to allow migration toward a more SQLAlchemy 0.4 style of use.
171 """
172 return self.app.model.context.current
173 def log_event( self, message, tool_id=None, **kwargs ):
174 """
175 Application level logging. Still needs fleshing out (log levels and such)
176 Logging events is a config setting - if False, do not log.
177 """
178 if self.app.config.log_events:
179 event = self.app.model.Event()
180 event.tool_id = tool_id
181 try:
182 event.message = message % kwargs
183 except:
184 event.message = message
185 try:
186 event.history = self.get_history()
187 except:
188 event.history = None
189 try:
190 event.history_id = self.history.id
191 except:
192 event.history_id = None
193 try:
194 event.user = self.user
195 except:
196 event.user = None
197 try:
198 event.session_id = self.galaxy_session.id
199 except:
200 event.session_id = None
201 event.flush()
202 def get_cookie( self, name='galaxysession' ):
203 """Convenience method for getting a session cookie"""
204 try:
205 # If we've changed the cookie during the request return the new value
206 if name in self.response.cookies:
207 return self.response.cookies[name].value
208 else:
209 return self.request.cookies[name].value
210 except:
211 return None
212 def set_cookie( self, value, name='galaxysession', path='/', age=90, version='1' ):
213 """Convenience method for setting a session cookie"""
214 # The galaxysession cookie value must be a high entropy 128 bit random number encrypted
215 # using a server secret key. Any other value is invalid and could pose security issues.
216 self.response.cookies[name] = value
217 self.response.cookies[name]['path'] = path
218 self.response.cookies[name]['max-age'] = 3600 * 24 * age # 90 days
219 tstamp = time.localtime ( time.time() + 3600 * 24 * age )
220 self.response.cookies[name]['expires'] = time.strftime( '%a, %d-%b-%Y %H:%M:%S GMT', tstamp )
221 self.response.cookies[name]['version'] = version
222 #@property
223 #def galaxy_session( self ):
224 # if not self.__galaxy_session:
225 # self.__ensure_valid_session()
226 # return self.__galaxy_session
227 def __ensure_valid_session( self, session_cookie ):
228 """
229 Ensure that a valid Galaxy session exists and is available as
230 trans.session (part of initialization)
231
232 Support for universe_session and universe_user cookies has been
233 removed as of 31 Oct 2008.
234 """
235 sa_session = self.sa_session
236 # Try to load an existing session
237 secure_id = self.get_cookie( name=session_cookie )
238 galaxy_session = None
239 prev_galaxy_session = None
240 user_for_new_session = None
241 invalidate_existing_session = False
242 # Track whether the session has changed so we can avoid calling flush
243 # in the most common case (session exists and is valid).
244 galaxy_session_requires_flush = False
245 if secure_id:
246 # Decode the cookie value to get the session_key
247 session_key = self.security.decode_session_key( secure_id )
248 try:
249 # Make sure we have a valid UTF-8 string
250 session_key = session_key.encode( 'utf8' )
251 except UnicodeDecodeError:
252 # We'll end up creating a new galaxy_session
253 session_key = None
254 if session_key:
255 # Retrieve the galaxy_session id via the unique session_key
256 galaxy_session = self.app.model.GalaxySession.filter( and_( self.app.model.GalaxySession.table.c.session_key==session_key,
257 self.app.model.GalaxySession.table.c.is_valid==True ) ).first()
258 # If remote user is in use it can invalidate the session, so we need to to check some things now.
259 if self.app.config.use_remote_user:
260 assert "HTTP_REMOTE_USER" in self.environ, \
261 "use_remote_user is set but no HTTP_REMOTE_USER variable"
262 remote_user_email = self.environ[ 'HTTP_REMOTE_USER' ]
263 if galaxy_session:
264 # An existing session, make sure correct association exists
265 if galaxy_session.user is None:
266 # No user, associate
267 galaxy_session.user = self.__get_or_create_remote_user( remote_user_email )
268 galaxy_session_requires_flush = True
269 elif galaxy_session.user.email != remote_user_email:
270 # Session exists but is not associated with the correct remote user
271 invalidate_existing_session = True
272 user_for_new_session = self.__get_or_create_remote_user( remote_user_email )
273 log.warning( "User logged in as '%s' externally, but has a cookie as '%s' invalidating session",
274 remote_user_email, galaxy_session.user.email )
275 else:
276 # No session exists, get/create user for new session
277 user_for_new_session = self.__get_or_create_remote_user( remote_user_email )
278 else:
279 if galaxy_session is not None and galaxy_session.user and galaxy_session.user.external:
280 # Remote user support is not enabled, but there is an existing
281 # session with an external user, invalidate
282 invalidate_existing_session = True
283 log.warning( "User '%s' is an external user with an existing session, invalidating session since external auth is disabled",
284 galaxy_session.user.email )
285 elif galaxy_session is not None and galaxy_session.user is not None and galaxy_session.user.deleted:
286 invalidate_existing_session = True
287 log.warning( "User '%s' is marked deleted, invalidating session" % galaxy_session.user.email )
288 # Do we need to invalidate the session for some reason?
289 if invalidate_existing_session:
290 prev_galaxy_session = galaxy_session
291 prev_galaxy_session.is_valid = False
292 galaxy_session = None
293 # No relevant cookies, or couldn't find, or invalid, so create a new session
294 if galaxy_session is None:
295 galaxy_session = self.__create_new_session( prev_galaxy_session, user_for_new_session )
296 galaxy_session_requires_flush = True
297 self.galaxy_session = galaxy_session
298 self.__update_session_cookie( name=session_cookie )
299 else:
300 self.galaxy_session = galaxy_session
301 # Do we need to flush the session?
302 if galaxy_session_requires_flush:
303 objects_to_flush = [ galaxy_session ]
304 # FIXME: If prev_session is a proper relation this would not
305 # be needed.
306 if prev_galaxy_session:
307 objects_to_flush.append( prev_galaxy_session )
308 sa_session.flush( objects_to_flush )
309 # If the old session was invalid, get a new history with our new session
310 if invalidate_existing_session:
311 self.new_history()
312 def __ensure_logged_in_user( self, environ ):
313 allowed_paths = (
314 url_for( controller='root', action='index' ),
315 url_for( controller='root', action='tool_menu' ),
316 url_for( controller='root', action='masthead' ),
317 url_for( controller='root', action='history' ),
318 url_for( controller='user', action='login' ),
319 url_for( controller='user', action='create' ),
320 url_for( controller='user', action='reset_password' ),
321 url_for( controller='library', action='browse' )
322 )
323 display_as = url_for( controller='root', action='display_as' )
324 if self.galaxy_session.user is None:
325 if self.app.config.ucsc_display_sites and self.request.path == display_as:
326 try:
327 host = socket.gethostbyaddr( self.environ[ 'REMOTE_ADDR' ] )[0]
328 except( socket.error, socket.herror, socket.gaierror, socket.timeout ):
329 host = None
330 if host in UCSC_SERVERS:
331 return
332 if self.request.path not in allowed_paths:
333 self.response.send_redirect( url_for( controller='root', action='index' ) )
334 def __create_new_session( self, prev_galaxy_session=None, user_for_new_session=None ):
335 """
336 Create a new GalaxySession for this request, possibly with a connection
337 to a previous session (in `prev_galaxy_session`) and an existing user
338 (in `user_for_new_session`).
339
340 Caller is responsible for flushing the returned session.
341 """
342 session_key = self.security.get_new_session_key()
343 galaxy_session = self.app.model.GalaxySession(
344 session_key=session_key,
345 is_valid=True,
346 remote_host = self.request.remote_host,
347 remote_addr = self.request.remote_addr,
348 referer = self.request.headers.get( 'Referer', None ) )
349 if prev_galaxy_session:
350 # Invalidated an existing session for some reason, keep track
351 galaxy_session.prev_session_id = prev_galaxy_session.id
352 if user_for_new_session:
353 # The new session should be associated with the user
354 galaxy_session.user = user_for_new_session
355 return galaxy_session
356 def __get_or_create_remote_user( self, remote_user_email ):
357 """
358 Return the user in $HTTP_REMOTE_USER and create if necessary
359 """
360 # remote_user middleware ensures HTTP_REMOTE_USER exists
361 user = self.app.model.User.filter( self.app.model.User.table.c.email==remote_user_email ).first()
362 if user:
363 # GVK: June 29, 2009 - This is to correct the behavior of a previous bug where a private
364 # role and default user / history permissions were not set for remote users. When a
365 # remote user authenticates, we'll look for this information, and if missing, create it.
366 if not self.app.security_agent.get_private_user_role( user ):
367 self.app.security_agent.create_private_user_role( user )
368 if not user.default_permissions:
369 self.app.security_agent.user_set_default_permissions( user, history=True, dataset=True )
370 elif user is None:
371 random.seed()
372 user = self.app.model.User( email=remote_user_email )
373 user.set_password_cleartext( ''.join( random.sample( string.letters + string.digits, 12 ) ) )
374 user.external = True
375 user.flush()
376 self.app.security_agent.create_private_user_role( user )
377 # We set default user permissions, before we log in and set the default history permissions
378 self.app.security_agent.user_set_default_permissions( user )
379 #self.log_event( "Automatically created account '%s'", user.email )
380 return user
381 def __update_session_cookie( self, name='galaxysession' ):
382 """
383 Update the session cookie to match the current session.
384 """
385 self.set_cookie( self.security.encode_session_key( self.galaxy_session.session_key ), name=name )
386 def handle_user_login( self, user ):
387 """
388 Login a new user (possibly newly created)
389 - create a new session
390 - associate new session with user
391 - if old session had a history and it was not associated with a user, associate it with the new session,
392 otherwise associate the current session's history with the user
393 """
394 # Set the previous session
395 prev_galaxy_session = self.galaxy_session
396 prev_galaxy_session.is_valid = False
397 # Define a new current_session
398 self.galaxy_session = self.__create_new_session( prev_galaxy_session, user )
399 # Associated the current user's last accessed history (if exists) with their new session
400 history = None
401 try:
402 users_last_session = user.galaxy_sessions[0]
403 last_accessed = True
404 except:
405 users_last_session = None
406 last_accessed = False
407 if users_last_session and users_last_session.current_history:
408 history = users_last_session.current_history
409 if not history:
410 if prev_galaxy_session.current_history:
411 if prev_galaxy_session.current_history.user is None or prev_galaxy_session.current_history.user == user:
412 # If the previous galaxy session had a history, associate it with the new
413 # session, but only if it didn't belong to a different user.
414 history = prev_galaxy_session.current_history
415 elif self.galaxy_session.current_history:
416 history = self.galaxy_session.current_history
417 else:
418 history = self.get_history( create=True )
419 if history not in self.galaxy_session.histories:
420 self.galaxy_session.add_history( history )
421 if history.user is None:
422 history.user = user
423 self.galaxy_session.current_history = history
424 if not last_accessed:
425 # Only set default history permissions if current history is not from a previous session
426 self.app.security_agent.history_set_default_permissions( history, dataset=True, bypass_manage_permission=True )
427 self.sa_session.flush( [ prev_galaxy_session, self.galaxy_session, history ] )
428 # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
429 self.__update_session_cookie( name='galaxysession' )
430 def handle_user_logout( self ):
431 """
432 Logout the current user:
433 - invalidate the current session
434 - create a new session with no user associated
435 """
436 prev_galaxy_session = self.galaxy_session
437 prev_galaxy_session.is_valid = False
438 self.galaxy_session = self.__create_new_session( prev_galaxy_session )
439 self.sa_session.flush( [ prev_galaxy_session, self.galaxy_session ] )
440 # This method is not called from the Galaxy reports, so the cookie will always be galaxysession
441 self.__update_session_cookie( name='galaxysession' )
442
443 def get_galaxy_session( self ):
444 """
445 Return the current galaxy session
446 """
447 return self.galaxy_session
448
449 def get_history( self, create=False ):
450 """
451 Load the current history, creating a new one only if there is not
452 current history and we're told to create"
453 """
454 history = self.galaxy_session.current_history
455 if not history:
456 if util.string_as_bool( create ):
457 history = self.new_history()
458 else:
459 # Perhaps a bot is running a tool without having logged in to get a history
460 log.debug( "Error: this request returned None from get_history(): %s" % self.request.browser_url )
461 return None
462 return history
463 def set_history( self, history ):
464 if history and not history.deleted:
465 self.galaxy_session.current_history = history
466 self.sa_session.flush( [ self.galaxy_session ] )
467 history = property( get_history, set_history )
468 def new_history( self, name=None ):
469 """
470 Create a new history and associate it with the current session and
471 its associated user (if set).
472 """
473 # Create new history
474 history = self.app.model.History()
475 if name:
476 history.name = name
477 # Associate with session
478 history.add_galaxy_session( self.galaxy_session )
479 # Make it the session's current history
480 self.galaxy_session.current_history = history
481 # Associate with user
482 if self.galaxy_session.user:
483 history.user = self.galaxy_session.user
484 # Track genome_build with history
485 history.genome_build = util.dbnames.default_value
486 # Set the user's default history permissions
487 self.app.security_agent.history_set_default_permissions( history )
488 # Save
489 self.sa_session.flush( [ self.galaxy_session, history ] )
490 return history
491
492 def get_user( self ):
493 """Return the current user if logged in or None."""
494 return self.galaxy_session.user
495 def set_user( self, user ):
496 """Set the current user."""
497 self.galaxy_session.user = user
498 self.sa_session.flush( [ self.galaxy_session ] )
499 user = property( get_user, set_user )
500
501 def get_user_and_roles( self ):
502 user = self.get_user()
503 if user:
504 roles = user.all_roles()
505 else:
506 roles = []
507 return user, roles
508
509 def user_is_admin( self ):
510 admin_users = self.app.config.get( "admin_users", "" ).split( "," )
511 if self.user and admin_users and self.user.email in admin_users:
512 return True
513 return False
514
515 def get_toolbox(self):
516 """Returns the application toolbox"""
517 return self.app.toolbox
518 @base.lazy_property
519 def template_context( self ):
520 return dict()
521 @property
522 def model( self ):
523 return self.app.model
524 def make_form_data( self, name, **kwargs ):
525 rval = self.template_context[name] = FormData()
526 rval.values.update( kwargs )
527 return rval
528 def set_message( self, message ):
529 """
530 Convenience method for setting the 'message' element of the template
531 context.
532 """
533 self.template_context['message'] = message
534 def show_message( self, message, type='info', refresh_frames=[], cont=None ):
535 """
536 Convenience method for displaying a simple page with a single message.
537
538 `type`: one of "error", "warning", "info", or "done"; determines the
539 type of dialog box and icon displayed with the message
540
541 `refresh_frames`: names of frames in the interface that should be
542 refreshed when the message is displayed
543 """
544 return self.fill_template( "message.mako", message_type=type, message=message, refresh_frames=refresh_frames, cont=cont )
545 def show_error_message( self, message, refresh_frames=[] ):
546 """
547 Convenience method for displaying an error message. See `show_message`.
548 """
549 return self.show_message( message, 'error', refresh_frames )
550 def show_ok_message( self, message, refresh_frames=[] ):
551 """
552 Convenience method for displaying an ok message. See `show_message`.
553 """
554 return self.show_message( message, 'done', refresh_frames )
555 def show_warn_message( self, message, refresh_frames=[] ):
556 """
557 Convenience method for displaying an warn message. See `show_message`.
558 """
559 return self.show_message( message, 'warning', refresh_frames )
560 def show_form( self, form, header=None, template="form.mako" ):
561 """
562 Convenience method for displaying a simple page with a single HTML
563 form.
564 """
565 return self.fill_template( template, form=form, header=header )
566 def fill_template(self, filename, **kwargs):
567 """
568 Fill in a template, putting any keyword arguments on the context.
569 """
570 # call get_user so we can invalidate sessions from external users,
571 # if external auth has been disabled.
572 self.get_user()
573 if filename.endswith( ".mako" ):
574 return self.fill_template_mako( filename, **kwargs )
575 else:
576 template = Template( file=os.path.join(self.app.config.template_path, filename),
577 searchList=[kwargs, self.template_context, dict(caller=self, t=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app)] )
578 return str( template )
579 def fill_template_mako( self, filename, **kwargs ):
580 template = self.webapp.mako_template_lookup.get_template( filename )
581 template.output_encoding = 'utf-8'
582 data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app )
583 data.update( self.template_context )
584 data.update( kwargs )
585 return template.render( **data )
586 def stream_template_mako( self, filename, **kwargs ):
587 template = self.webapp.mako_template_lookup.get_template( filename )
588 template.output_encoding = 'utf-8'
589 data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app )
590 data.update( self.template_context )
591 data.update( kwargs )
592 ## return template.render( **data )
593 def render( environ, start_response ):
594 response_write = start_response( self.response.wsgi_status(), self.response.wsgi_headeritems() )
595 class StreamBuffer( object ):
596 def write( self, d ):
597 response_write( d.encode( 'utf-8' ) )
598 buffer = StreamBuffer()
599 context = mako.runtime.Context( buffer, **data )
600 template.render_context( context )
601 return []
602 return render
603 def fill_template_string(self, template_string, context=None, **kwargs):
604 """
605 Fill in a template, putting any keyword arguments on the context.
606 """
607 template = Template( source=template_string,
608 searchList=[context or kwargs, dict(caller=self)] )
609 return str(template)
610
611 @property
612 def db_builds( self ):
613 """
614 Returns the builds defined by galaxy and the builds defined by
615 the user (chromInfo in history).
616 """
617 dbnames = list()
618 datasets = self.app.model.HistoryDatasetAssociation \
619 .filter_by(deleted=False, history_id=self.history.id, extension="len").all()
620 if len(datasets) > 0:
621 dbnames.append( (util.dbnames.default_value, '--------- User Defined Builds ----------') )
622 for dataset in datasets:
623 dbnames.append( (dataset.dbkey, dataset.name) )
624 dbnames.extend( util.dbnames )
625 return dbnames
626
627 def db_dataset_for( self, dbkey ):
628 """
629 Returns the db_file dataset associated/needed by `dataset`, or `None`.
630 """
631 datasets = self.app.model.HistoryDatasetAssociation \
632 .filter_by(deleted=False, history_id=self.history.id, extension="len").all()
633 for ds in datasets:
634 if dbkey == ds.dbkey:
635 return ds
636 return None
637
638 def request_types(self):
639 if self.app.model.RequestType.query().all():
640 return True
641 return False
642
643class FormBuilder( object ):
644 """
645 Simple class describing an HTML form
646 """
647 def __init__( self, action="", title="", name="form", submit_text="submit" ):
648 self.title = title
649 self.name = name
650 self.action = action
651 self.submit_text = submit_text
652 self.inputs = []
653 def add_input( self, type, name, label, value=None, error=None, help=None, use_label=True ):
654 self.inputs.append( FormInput( type, label, name, value, error, help, use_label ) )
655 return self
656 def add_text( self, name, label, value=None, error=None, help=None ):
657 return self.add_input( 'text', label, name, value, error, help )
658 def add_password( self, name, label, value=None, error=None, help=None ):
659 return self.add_input( 'password', label, name, value, error, help )
660
661class FormInput( object ):
662 """
663 Simple class describing a form input element
664 """
665 def __init__( self, type, name, label, value=None, error=None, help=None, use_label=True ):
666 self.type = type
667 self.name = name
668 self.label = label
669 self.value = value
670 self.error = error
671 self.help = help
672 self.use_label = use_label
673
674class FormData( object ):
675 """
676 Class for passing data about a form to a template, very rudimentary, could
677 be combined with the tool form handling to build something more general.
678 """
679 def __init__( self ):
680 self.values = Bunch()
681 self.errors = Bunch()
682
683class Bunch( dict ):
684 """
685 Bunch based on a dict
686 """
687 def __getattr__( self, key ):
688 if key not in self: raise AttributeError, key
689 return self[key]
690 def __setattr__( self, key, value ):
691 self[key] = value
692