PageRenderTime 101ms CodeModel.GetById 44ms app.highlight 41ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/galaxy/webapps/community/controllers/common.py

https://bitbucket.org/h_morita_dbcls/galaxy-central
Python | 552 lines | 538 code | 6 blank | 8 comment | 16 complexity | d903c33b6ade000b876f1095622a8d6a MD5 | raw file
  1import tarfile
  2from galaxy.web.base.controller import *
  3from galaxy.webapps.community import model
  4from galaxy.model.orm import *
  5from galaxy.web.framework.helpers import time_ago, iff, grids
  6from galaxy.web.form_builder import SelectField
  7from galaxy.item_attrs.ratings import UsesItemRatings
  8import logging
  9log = logging.getLogger( __name__ )
 10
 11# States for passing messages
 12SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error"
 13
 14class ToolListGrid( grids.Grid ):
 15    class NameColumn( grids.TextColumn ):
 16        def get_value( self, trans, grid, tool ):
 17            return tool.name
 18    class TypeColumn( grids.GridColumn ):
 19        def get_value( self, trans, grid, tool ):
 20            if tool.is_suite:
 21                return 'Suite'
 22            return 'Tool'
 23    class VersionColumn( grids.TextColumn ):
 24        def get_value( self, trans, grid, tool ):
 25            return tool.version
 26    class DescriptionColumn( grids.TextColumn ):
 27        def get_value( self, trans, grid, tool ):
 28            return tool.description
 29    class CategoryColumn( grids.TextColumn ):
 30        def get_value( self, trans, grid, tool ):
 31            rval = '<ul>'
 32            if tool.categories:
 33                for tca in tool.categories:
 34                    rval += '<li><a href="browse_tools?operation=tools_by_category&id=%s&webapp=community">%s</a></li>' \
 35                        % ( trans.security.encode_id( tca.category.id ), tca.category.name )
 36            else:
 37                rval += '<li>not set</li>'
 38            rval += '</ul>'
 39            return rval
 40    class ToolCategoryColumn( grids.GridColumn ):
 41        def filter( self, trans, user, query, column_filter ):
 42            """Modify query to filter by category."""
 43            if column_filter == "All":
 44                pass
 45            return query.filter( model.Category.name == column_filter )
 46    class UserColumn( grids.TextColumn ):
 47        def get_value( self, trans, grid, tool ):
 48            if tool.user:
 49                return tool.user.username
 50            return 'no user'
 51    class EmailColumn( grids.GridColumn ):
 52        def filter( self, trans, user, query, column_filter ):
 53            if column_filter == 'All':
 54                return query
 55            return query.filter( and_( model.Tool.table.c.user_id == model.User.table.c.id,
 56                                       model.User.table.c.email == column_filter ) )
 57    # Grid definition
 58    title = "Tools"
 59    model_class = model.Tool
 60    template='/webapps/community/tool/grid.mako'
 61    default_sort_key = "name"
 62    columns = [
 63        NameColumn( "Name",
 64                    key="name",
 65                    link=( lambda item: dict( operation="view_tool", id=item.id, webapp="community" ) ),
 66                    model_class=model.Tool,
 67                    attach_popup=False
 68                    ),
 69        TypeColumn( "Type",
 70                     key="suite",
 71                     model_class=model.Tool,
 72                     attach_popup=False ),
 73        VersionColumn( "Version",
 74                       key="version",
 75                       model_class=model.Tool,
 76                       attach_popup=False,
 77                       filterable="advanced" ),
 78        DescriptionColumn( "Description",
 79                           key="description",
 80                           model_class=model.Tool,
 81                           attach_popup=False
 82                           ),
 83        CategoryColumn( "Category",
 84                        model_class=model.Category,
 85                        attach_popup=False,
 86                        filterable="advanced" ),
 87        UserColumn( "Uploaded By",
 88                    model_class=model.User,
 89                    link=( lambda item: dict( operation="tools_by_user", id=item.id, webapp="community" ) ),
 90                    attach_popup=False,
 91                    filterable="advanced" ),
 92        # Columns that are valid for filtering but are not visible.
 93        EmailColumn( "Email",
 94                     key="email",
 95                     model_class=model.User,
 96                     visible=False ),
 97        ToolCategoryColumn( "Category",
 98                            key="category",
 99                            model_class=model.Category,
100                            visible=False )
101    ]
102    columns.append( grids.MulticolFilterColumn( "Search", 
103                                                cols_to_filter=[ columns[0], columns[1], columns[2] ],
104                                                key="free-text-search",
105                                                visible=False,
106                                                filterable="standard" ) )
107    operations = []
108    standard_filters = []
109    default_filter = {}
110    num_rows_per_page = 50
111    preserve_state = False
112    use_paging = True
113    def build_initial_query( self, trans, **kwd ):
114        return trans.sa_session.query( self.model_class ) \
115                               .join( model.ToolEventAssociation.table ) \
116                               .join( model.Event.table ) \
117                               .outerjoin( model.ToolCategoryAssociation.table ) \
118                               .outerjoin( model.Category.table )
119
120class CategoryListGrid( grids.Grid ):
121    class NameColumn( grids.TextColumn ):
122        def get_value( self, trans, grid, category ):
123            return category.name
124    class DescriptionColumn( grids.TextColumn ):
125        def get_value( self, trans, grid, category ):
126            return category.description
127    class ToolsColumn( grids.TextColumn ):
128        def get_value( self, trans, grid, category ):
129            if category.tools:
130                viewable_tools = 0
131                for tca in category.tools:
132                    viewable_tools += 1
133                return viewable_tools
134            return 0
135
136    # Grid definition
137    webapp = "community"
138    title = "Categories"
139    model_class = model.Category
140    template='/webapps/community/category/grid.mako'
141    default_sort_key = "name"
142    columns = [
143        NameColumn( "Name",
144                    key="name",
145                    model_class=model.Category,
146                    link=( lambda item: dict( operation="tools_by_category", id=item.id, webapp="community" ) ),
147                    attach_popup=False,
148                    filterable="advanced"
149                  ),
150        DescriptionColumn( "Description",
151                    key="description",
152                    model_class=model.Category,
153                    attach_popup=False,
154                    filterable="advanced"
155                  ),
156        # Columns that are valid for filtering but are not visible.
157        grids.DeletedColumn( "Deleted",
158                             key="deleted",
159                             visible=False,
160                             filterable="advanced" ),
161        ToolsColumn( "Tools",
162                     model_class=model.Tool,
163                     attach_popup=False )
164    ]
165    columns.append( grids.MulticolFilterColumn( "Search",
166                                                cols_to_filter=[ columns[0], columns[1] ],
167                                                key="free-text-search",
168                                                visible=False,
169                                                filterable="standard" ) )
170
171    # Override these
172    global_actions = []
173    operations = []
174    standard_filters = []
175    num_rows_per_page = 50
176    preserve_state = False
177    use_paging = True
178
179class ItemRatings( UsesItemRatings ):
180    """Overrides rate_item method since we also allow for comments"""
181    def rate_item( self, trans, user, item, rating, comment='' ):
182        """ Rate an item. Return type is <item_class>RatingAssociation. """
183        item_rating = self.get_user_item_rating( trans, user, item )
184        if not item_rating:
185            # User has not yet rated item; create rating.
186            item_rating_assoc_class = self._get_item_rating_assoc_class( trans, item )
187            item_rating = item_rating_assoc_class()
188            item_rating.user = trans.user
189            item_rating.set_item( item )
190            item_rating.rating = rating
191            item_rating.comment = comment
192            trans.sa_session.add( item_rating )
193            trans.sa_session.flush()
194        elif item_rating.rating != rating or item_rating.comment != comment:
195            # User has previously rated item; update rating.
196            item_rating.rating = rating
197            item_rating.comment = comment
198            trans.sa_session.flush()
199        return item_rating
200                
201class CommonController( BaseController, ItemRatings ):
202    @web.expose
203    def edit_tool( self, trans, cntrller, **kwd ):
204        params = util.Params( kwd )
205        message = util.restore_text( params.get( 'message', ''  ) )
206        status = params.get( 'status', 'done' )
207        id = params.get( 'id', None )
208        if not id:
209            return trans.response.send_redirect( web.url_for( controller=cntrller,
210                                                              action='browse_tools',
211                                                              message='Select a tool to edit',
212                                                              status='error' ) )
213        tool = get_tool( trans, id )
214        can_edit = trans.app.security_agent.can_edit( trans.user, trans.user_is_admin(), cntrller, tool )
215        if not can_edit:
216            return trans.response.send_redirect( web.url_for( controller=cntrller,
217                                                              action='browse_tools',
218                                                              message='You are not allowed to edit this tool',
219                                                              status='error' ) )
220        if params.get( 'edit_tool_button', False ):
221            if params.get( 'in_categories', False ):
222                in_categories = [ trans.sa_session.query( trans.app.model.Category ).get( x ) for x in util.listify( params.in_categories ) ]
223                trans.app.security_agent.set_entity_category_associations( tools=[ tool ], categories=in_categories )
224            else:
225                # There must not be any categories associated with the tool
226                trans.app.security_agent.set_entity_category_associations( tools=[ tool ], categories=[] )
227            user_description = util.restore_text( params.get( 'user_description', '' ) )
228            if user_description:
229                tool.user_description = user_description
230            else:
231                tool.user_description = ''
232            trans.sa_session.add( tool )
233            trans.sa_session.flush()
234            message = "Tool '%s' description and category associations have been saved" % tool.name
235            return trans.response.send_redirect( web.url_for( controller='common',
236                                                              action='edit_tool',
237                                                              cntrller=cntrller,
238                                                              id=id,
239                                                              message=message,
240                                                              status='done' ) )
241        elif params.get( 'approval_button', False ):
242            user_description = util.restore_text( params.get( 'user_description', '' ) )
243            if user_description:
244                tool.user_description = user_description
245                if params.get( 'in_categories', False ):
246                    in_categories = [ trans.sa_session.query( trans.app.model.Category ).get( x ) for x in util.listify( params.in_categories ) ]
247                    trans.app.security_agent.set_entity_category_associations( tools=[ tool ], categories=in_categories )
248                else:
249                    # There must not be any categories associated with the tool
250                    trans.app.security_agent.set_entity_category_associations( tools=[ tool ], categories=[] )
251                trans.sa_session.add( tool )
252                trans.sa_session.flush()
253                # Move the state from NEW to WAITING
254                event = trans.app.model.Event( state=trans.app.model.Tool.states.WAITING )
255                tea = trans.app.model.ToolEventAssociation( tool, event )
256                trans.sa_session.add_all( ( event, tea ) )
257                trans.sa_session.flush()
258                message = "Tool '%s' has been submitted for approval and can no longer be modified" % ( tool.name )
259                return trans.response.send_redirect( web.url_for( controller='common',
260                                                                  action='view_tool',
261                                                                  cntrller=cntrller,
262                                                                  id=id,
263                                                                  message=message,
264                                                                  status='done' ) )
265            else:
266                # The user_description field is required when submitting for approval
267                message = 'A user description is required prior to approval.'
268                status = 'error'
269        in_categories = []
270        out_categories = []
271        for category in get_categories( trans ):
272            if category in [ x.category for x in tool.categories ]:
273                in_categories.append( ( category.id, category.name ) )
274            else:
275                out_categories.append( ( category.id, category.name ) )
276        if tool.is_rejected:
277            # Include the comments regarding the reason for rejection
278            reason_for_rejection = tool.latest_event.comment
279        else:
280            reason_for_rejection = ''
281        can_approve_or_reject = trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), cntrller, tool )
282        can_delete = trans.app.security_agent.can_delete( trans.user, trans.user_is_admin(), cntrller, tool )
283        can_download = trans.app.security_agent.can_download( trans.user, trans.user_is_admin(), cntrller, tool )
284        can_purge = trans.app.security_agent.can_purge( trans.user, trans.user_is_admin(), cntrller )
285        can_upload_new_version = trans.app.security_agent.can_upload_new_version( trans.user, tool )
286        can_view = trans.app.security_agent.can_view( trans.user, trans.user_is_admin(), cntrller, tool )
287        return trans.fill_template( '/webapps/community/tool/edit_tool.mako',
288                                    cntrller=cntrller,
289                                    tool=tool,
290                                    id=id,
291                                    in_categories=in_categories,
292                                    out_categories=out_categories,
293                                    can_approve_or_reject=can_approve_or_reject,
294                                    can_delete=can_delete,
295                                    can_download=can_download,
296                                    can_edit=can_edit,
297                                    can_purge=can_purge,
298                                    can_upload_new_version=can_upload_new_version,
299                                    can_view=can_view,
300                                    reason_for_rejection=reason_for_rejection,
301                                    message=message,
302                                    status=status )
303    @web.expose
304    def view_tool( self, trans, cntrller, **kwd ):
305        params = util.Params( kwd )
306        message = util.restore_text( params.get( 'message', ''  ) )
307        status = params.get( 'status', 'done' )
308        id = params.get( 'id', None )
309        if not id:
310            return trans.response.send_redirect( web.url_for( controller=cntrller,
311                                                              action='browse_tools',
312                                                              message='Select a tool to view',
313                                                              status='error' ) )
314        tool = get_tool( trans, id )
315        can_view = trans.app.security_agent.can_view( trans.user, trans.user_is_admin(), cntrller, tool )
316        if not can_view:
317            return trans.response.send_redirect( web.url_for( controller=cntrller,
318                                                              action='browse_tools',
319                                                              message='You are not allowed to view this tool',
320                                                              status='error' ) )
321        can_approve_or_reject = trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), cntrller, tool )
322        can_delete = trans.app.security_agent.can_delete( trans.user, trans.user_is_admin(), cntrller, tool )
323        can_download = trans.app.security_agent.can_download( trans.user, trans.user_is_admin(), cntrller, tool )
324        can_edit = trans.app.security_agent.can_edit( trans.user, trans.user_is_admin(), cntrller, tool )
325        can_purge = trans.app.security_agent.can_purge( trans.user, trans.user_is_admin(), cntrller )
326        can_rate = trans.app.security_agent.can_rate( trans.user, trans.user_is_admin(), cntrller, tool )
327        can_upload_new_version = trans.app.security_agent.can_upload_new_version( trans.user, tool )
328        visible_versions = trans.app.security_agent.get_visible_versions( trans.user, trans.user_is_admin(), cntrller, tool )
329        categories = [ tca.category for tca in tool.categories ]
330        tool_file_contents = tarfile.open( tool.file_name, 'r' ).getnames()
331        if tool.is_rejected:
332            # Include the comments regarding the reason for rejection
333            reason_for_rejection = tool.latest_event.comment
334        else:
335            reason_for_rejection = ''
336        return trans.fill_template( '/webapps/community/tool/view_tool.mako',
337                                    tool=tool,
338                                    tool_file_contents=tool_file_contents,
339                                    categories=categories,
340                                    cntrller=cntrller,
341                                    can_approve_or_reject=can_approve_or_reject,
342                                    can_delete=can_delete,
343                                    can_download=can_download,
344                                    can_edit=can_edit,
345                                    can_purge=can_purge,
346                                    can_rate=can_rate,
347                                    can_upload_new_version=can_upload_new_version,
348                                    can_view=can_view,
349                                    visible_versions=visible_versions,
350                                    reason_for_rejection=reason_for_rejection,
351                                    message=message,
352                                    status=status )
353    @web.expose
354    def delete_tool( self, trans, cntrller, **kwd ):
355        params = util.Params( kwd )
356        message = util.restore_text( params.get( 'message', ''  ) )
357        status = params.get( 'status', 'done' )
358        id = params.get( 'id', None )
359        if not id:
360            message='Select a tool to delete'
361            status='error'
362        else:
363            tool = get_tool( trans, id )
364            if not trans.app.security_agent.can_delete( trans.user, trans.user_is_admin(), cntrller, tool ):
365                return trans.response.send_redirect( web.url_for( controller=cntrller,
366                                                                  action='browse_tools',
367                                                                  message='You are not allowed to delete this tool',
368                                                                  status='error' ) )
369            # Create a new event
370            event = trans.model.Event( state=trans.model.Tool.states.DELETED )
371            # Flush so we can get an event id
372            trans.sa_session.add( event )
373            trans.sa_session.flush()
374            # Associate the tool with the event
375            tea = trans.model.ToolEventAssociation( tool=tool, event=event )
376            # Delete the tool, keeping state for categories, events and versions
377            tool.deleted = True
378            trans.sa_session.add_all( ( tool, tea ) )
379            trans.sa_session.flush()
380            # TODO: What if the tool has versions, should they all be deleted?
381            message = "Tool '%s' has been marked deleted" % tool.name
382            status = 'done'
383        return trans.response.send_redirect( web.url_for( controller=cntrller,
384                                                          action='browse_tools',
385                                                          message=message,
386                                                          status=status ) )
387    @web.expose
388    def download_tool( self, trans, cntrller, **kwd ):
389        params = util.Params( kwd )
390        id = params.get( 'id', None )
391        if not id:
392            return trans.response.send_redirect( web.url_for( controller='tool',
393                                                              action='browse_tools',
394                                                              message='Select a tool to download',
395                                                              status='error' ) )
396        tool = get_tool( trans, id )
397        if not trans.app.security_agent.can_download( trans.user, trans.user_is_admin(), cntrller, tool ):
398            return trans.response.send_redirect( web.url_for( controller=cntrller,
399                                                              action='browse_tools',
400                                                              message='You are not allowed to download this tool',
401                                                              status='error' ) )
402        trans.response.set_content_type( tool.mimetype )
403        trans.response.headers['Content-Length'] = int( os.stat( tool.file_name ).st_size )
404        trans.response.headers['Content-Disposition'] = 'attachment; filename=%s' % tool.download_file_name
405        return open( tool.file_name )
406    @web.expose
407    def upload_new_tool_version( self, trans, cntrller, **kwd ):
408        params = util.Params( kwd )
409        message = util.restore_text( params.get( 'message', ''  ) )
410        status = params.get( 'status', 'done' )
411        id = params.get( 'id', None )
412        if not id:
413            return trans.response.send_redirect( web.url_for( controller=cntrller,
414                                                              action='browse_tools',
415                                                              message='Select a tool to upload a new version',
416                                                              status='error' ) )
417        tool = get_tool( trans, id )
418        if not trans.app.security_agent.can_upload_new_version( trans.user, tool ):
419            return trans.response.send_redirect( web.url_for( controller=cntrller,
420                                                              action='browse_tools',
421                                                              message='You are not allowed to upload a new version of this tool',
422                                                              status='error' ) )
423        return trans.response.send_redirect( web.url_for( controller='upload',
424                                                          action='upload',
425                                                          message=message,
426                                                          status=status,
427                                                          replace_id=id ) )
428    @web.expose
429    @web.require_login( "view tool history" )
430    def view_tool_history( self, trans, cntrller, **kwd ):
431        params = util.Params( kwd )
432        message = util.restore_text( params.get( 'message', ''  ) )
433        status = params.get( 'status', 'done' )
434        id = params.get( 'id', None )
435        if not id:
436            return trans.response.send_redirect( web.url_for( controller=cntrller,
437                                                              action='browse_tools',
438                                                              message='Select a tool to view its history',
439                                                              status='error' ) )
440        tool = get_tool( trans, id )
441        can_view = trans.app.security_agent.can_view( trans.user, trans.user_is_admin(), cntrller, tool )
442        if not can_view:
443            return trans.response.send_redirect( web.url_for( controller=cntrller,
444                                                              action='browse_tools',
445                                                              message="You are not allowed to view this tool's history",
446                                                              status='error' ) )
447        can_approve_or_reject = trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), cntrller, tool )
448        can_edit = trans.app.security_agent.can_edit( trans.user, trans.user_is_admin(), cntrller, tool )
449        can_delete = trans.app.security_agent.can_delete( trans.user, trans.user_is_admin(), cntrller, tool )
450        can_download = trans.app.security_agent.can_download( trans.user, trans.user_is_admin(), cntrller, tool )
451        events = [ tea.event for tea in tool.events ]
452        events = [ ( event.state, time_ago( event.update_time ), event.comment ) for event in events ]
453        return trans.fill_template( '/webapps/community/common/view_tool_history.mako', 
454                                    cntrller=cntrller,
455                                    events=events,
456                                    tool=tool,
457                                    can_approve_or_reject=can_approve_or_reject,
458                                    can_edit=can_edit,
459                                    can_delete=can_delete,
460                                    can_download=can_download,
461                                    can_view=can_view,
462                                    message=message,
463                                    status=status )
464    @web.expose
465    @web.require_login( "rate tools" )
466    def rate_tool( self, trans, cntrller, **kwd ):
467        """ Rate a tool and return updated rating data. """
468        params = util.Params( kwd )
469        message = util.restore_text( params.get( 'message', ''  ) )
470        status = params.get( 'status', 'done' )
471        id = params.get( 'id', None )
472        if not id:
473            return trans.response.send_redirect( web.url_for( controller=cntrller,
474                                                              action='browse_tools',
475                                                              message='Select a tool to rate',
476                                                              status='error' ) )
477        tool = get_tool( trans, id )
478        can_rate = trans.app.security_agent.can_rate( trans.user, trans.user_is_admin(), cntrller, tool )
479        if not can_rate:
480            return trans.response.send_redirect( web.url_for( controller=cntrller,
481                                                              action='browse_tools',
482                                                              message="You are not allowed to rate this tool",
483                                                              status='error' ) )
484        if params.get( 'rate_button', False ):
485            rating = int( params.get( 'rating', '0' ) )
486            comment = util.restore_text( params.get( 'comment', '' ) )
487            rating = self.rate_item( trans, trans.user, tool, rating, comment )
488        avg_rating, num_ratings = self.get_ave_item_rating_data( trans, tool )
489        can_approve_or_reject = trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), cntrller, tool )
490        can_edit = trans.app.security_agent.can_edit( trans.user, trans.user_is_admin(), cntrller, tool )
491        can_delete = trans.app.security_agent.can_delete( trans.user, trans.user_is_admin(), cntrller, tool )
492        can_download = trans.app.security_agent.can_download( trans.user, trans.user_is_admin(), cntrller, tool )
493        display_reviews = util.string_as_bool( params.get( 'display_reviews', False ) )
494        tra = self.get_user_item_rating( trans, trans.user, tool )
495        return trans.fill_template( '/webapps/community/common/rate_tool.mako', 
496                                    cntrller=cntrller,
497                                    tool=tool,
498                                    avg_rating=avg_rating,
499                                    can_approve_or_reject=can_approve_or_reject,
500                                    can_edit=can_edit,
501                                    can_delete=can_delete,
502                                    can_download=can_download,
503                                    can_rate=can_rate,
504                                    display_reviews=display_reviews,
505                                    num_ratings=num_ratings,
506                                    tra=tra,
507                                    message=message,
508                                    status=status )
509
510## ---- Utility methods -------------------------------------------------------
511
512def get_versions( item ):
513    """Get all versions of item"""
514    versions = [ item ]
515    this_item = item
516    while item.newer_version:
517        versions.insert( 0, item.newer_version )
518        item = item.newer_version
519    item = this_item
520    while item.older_version:
521        versions.append( item.older_version[ 0 ] )
522        item = item.older_version[ 0 ]
523    return versions
524def get_categories( trans ):
525    """Get all categories from the database"""
526    return trans.sa_session.query( trans.model.Category ) \
527                           .filter( trans.model.Category.table.c.deleted==False ) \
528                           .order_by( trans.model.Category.table.c.name ).all()
529def get_category( trans, id ):
530    """Get a category from the database"""
531    return trans.sa_session.query( trans.model.Category ).get( trans.security.decode_id( id ) )
532def get_tool( trans, id ):
533    """Get a tool from the database"""
534    return trans.sa_session.query( trans.model.Tool ).get( trans.security.decode_id( id ) )
535def get_latest_versions_of_tools( trans ):
536    """Get only the latest version of each tool from the database"""
537    return trans.sa_session.query( trans.model.Tool ) \
538                           .filter( trans.model.Tool.newer_version_id == None ) \
539                           .order_by( trans.model.Tool.name )
540def get_approved_tools( trans ):
541    """Get the tools from the database whose state is APPROVED"""
542    approved_tools = []
543    for tool in get_latest_versions_of_tools( trans ):
544        if tool.state == trans.model.Tool.states.APPROVED:
545            approved_tools.append( tool )
546    return approved_tools
547def get_event( trans, id ):
548    """Get an event from the databse"""
549    return trans.sa_session.query( trans.model.Event ).get( trans.security.decode_id( id ) )
550def get_user( trans, id ):
551    """Get a user from the database"""
552    return trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( id ) )