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

https://bitbucket.org/h_morita_dbcls/galaxy-central · Python · 552 lines · 511 code · 9 blank · 32 comment · 55 complexity · d903c33b6ade000b876f1095622a8d6a MD5 · raw file

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