/lib/galaxy/web/controllers/page.py

https://bitbucket.org/cistrome/cistrome-harvard/ · Python · 762 lines · 742 code · 5 blank · 15 comment · 23 complexity · e356920c48742dc2e12b84a0e86ade1a MD5 · raw file

  1. from galaxy import model
  2. from galaxy.model.item_attrs import *
  3. from galaxy.web.base.controller import *
  4. from galaxy.web.framework.helpers import time_ago, grids
  5. from galaxy.util.sanitize_html import sanitize_html, _BaseHTMLProcessor
  6. from galaxy.util.odict import odict
  7. from galaxy.util.json import from_json_string
  8. def format_bool( b ):
  9. if b:
  10. return "yes"
  11. else:
  12. return ""
  13. class PageListGrid( grids.Grid ):
  14. # Custom column.
  15. class URLColumn( grids.PublicURLColumn ):
  16. def get_value( self, trans, grid, item ):
  17. return url_for( action='display_by_username_and_slug', username=item.user.username, slug=item.slug )
  18. # Grid definition
  19. use_panels = True
  20. title = "Pages"
  21. model_class = model.Page
  22. default_filter = { "published" : "All", "tags" : "All", "title" : "All", "sharing" : "All" }
  23. default_sort_key = "-create_time"
  24. columns = [
  25. grids.TextColumn( "Title", key="title", attach_popup=True, filterable="advanced" ),
  26. URLColumn( "Public URL" ),
  27. grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_annotation_association_class=model.PageAnnotationAssociation, filterable="advanced" ),
  28. grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.PageTagAssociation, filterable="advanced", grid_name="PageListGrid" ),
  29. grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False ),
  30. grids.GridColumn( "Created", key="create_time", format=time_ago ),
  31. grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
  32. ]
  33. columns.append(
  34. grids.MulticolFilterColumn(
  35. "Search",
  36. cols_to_filter=[ columns[0], columns[2] ],
  37. key="free-text-search", visible=False, filterable="standard" )
  38. )
  39. global_actions = [
  40. grids.GridAction( "Add new page", dict( action='create' ) )
  41. ]
  42. operations = [
  43. grids.DisplayByUsernameAndSlugGridOperation( "View", allow_multiple=False ),
  44. grids.GridOperation( "Edit content", allow_multiple=False, url_args=dict( action='edit_content') ),
  45. grids.GridOperation( "Edit attributes", allow_multiple=False, url_args=dict( action='edit') ),
  46. grids.GridOperation( "Share or Publish", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ),
  47. grids.GridOperation( "Delete", confirm="Are you sure you want to delete this page?" ),
  48. ]
  49. def apply_query_filter( self, trans, query, **kwargs ):
  50. return query.filter_by( user=trans.user, deleted=False )
  51. class PageAllPublishedGrid( grids.Grid ):
  52. # Grid definition
  53. use_panels = True
  54. use_async = True
  55. title = "Published Pages"
  56. model_class = model.Page
  57. default_sort_key = "update_time"
  58. default_filter = dict( title="All", username="All" )
  59. columns = [
  60. grids.PublicURLColumn( "Title", key="title", filterable="advanced" ),
  61. grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_annotation_association_class=model.PageAnnotationAssociation, filterable="advanced" ),
  62. grids.OwnerColumn( "Owner", key="username", model_class=model.User, filterable="advanced" ),
  63. grids.CommunityRatingColumn( "Community Rating", key="rating" ),
  64. grids.CommunityTagsColumn( "Community Tags", key="tags", model_tag_association_class=model.PageTagAssociation, filterable="advanced", grid_name="PageAllPublishedGrid" ),
  65. grids.ReverseSortColumn( "Last Updated", key="update_time", format=time_ago )
  66. ]
  67. columns.append(
  68. grids.MulticolFilterColumn(
  69. "Search title, annotation, owner, and tags",
  70. cols_to_filter=[ columns[0], columns[1], columns[2], columns[4] ],
  71. key="free-text-search", visible=False, filterable="standard" )
  72. )
  73. def build_initial_query( self, trans, **kwargs ):
  74. # Join so that searching history.user makes sense.
  75. return trans.sa_session.query( self.model_class ).join( model.User.table )
  76. def apply_query_filter( self, trans, query, **kwargs ):
  77. return query.filter( self.model_class.deleted==False ).filter( self.model_class.published==True )
  78. class ItemSelectionGrid( grids.Grid ):
  79. """ Base class for pages' item selection grids. """
  80. # Custom columns.
  81. class NameColumn( grids.TextColumn ):
  82. def get_value(self, trans, grid, item):
  83. if hasattr( item, "get_display_name" ):
  84. return item.get_display_name()
  85. else:
  86. return item.name
  87. # Grid definition.
  88. template = "/page/select_items_grid.mako"
  89. async_template = "/page/select_items_grid_async.mako"
  90. default_filter = { "deleted" : "False" , "sharing" : "All" }
  91. default_sort_key = "-update_time"
  92. use_async = True
  93. use_paging = True
  94. num_rows_per_page = 10
  95. def apply_query_filter( self, trans, query, **kwargs ):
  96. return query.filter_by( user=trans.user )
  97. class HistorySelectionGrid( ItemSelectionGrid ):
  98. """ Grid for selecting histories. """
  99. # Grid definition.
  100. title = "Saved Histories"
  101. model_class = model.History
  102. columns = [
  103. ItemSelectionGrid.NameColumn( "Name", key="name", filterable="advanced" ),
  104. grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.HistoryTagAssociation, filterable="advanced"),
  105. grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
  106. # Columns that are valid for filtering but are not visible.
  107. grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ),
  108. grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False, visible=False ),
  109. ]
  110. columns.append(
  111. grids.MulticolFilterColumn(
  112. "Search",
  113. cols_to_filter=[ columns[0], columns[1] ],
  114. key="free-text-search", visible=False, filterable="standard" )
  115. )
  116. def apply_query_filter( self, trans, query, **kwargs ):
  117. return query.filter_by( user=trans.user, purged=False )
  118. class HistoryDatasetAssociationSelectionGrid( ItemSelectionGrid ):
  119. """ Grid for selecting HDAs. """
  120. # Grid definition.
  121. title = "Saved Datasets"
  122. model_class = model.HistoryDatasetAssociation
  123. columns = [
  124. ItemSelectionGrid.NameColumn( "Name", key="name", filterable="advanced" ),
  125. grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.HistoryDatasetAssociationTagAssociation, filterable="advanced"),
  126. grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
  127. # Columns that are valid for filtering but are not visible.
  128. grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ),
  129. grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False, visible=False ),
  130. ]
  131. columns.append(
  132. grids.MulticolFilterColumn(
  133. "Search",
  134. cols_to_filter=[ columns[0], columns[1] ],
  135. key="free-text-search", visible=False, filterable="standard" )
  136. )
  137. def apply_query_filter( self, trans, query, **kwargs ):
  138. # To filter HDAs by user, need to join HDA and History table and then filter histories by user. This is necessary because HDAs do not have
  139. # a user relation.
  140. return query.select_from( model.HistoryDatasetAssociation.table.join( model.History.table ) ).filter( model.History.user == trans.user )
  141. class WorkflowSelectionGrid( ItemSelectionGrid ):
  142. """ Grid for selecting workflows. """
  143. # Grid definition.
  144. title = "Saved Workflows"
  145. model_class = model.StoredWorkflow
  146. columns = [
  147. ItemSelectionGrid.NameColumn( "Name", key="name", filterable="advanced" ),
  148. grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.StoredWorkflowTagAssociation, filterable="advanced"),
  149. grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
  150. # Columns that are valid for filtering but are not visible.
  151. grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ),
  152. grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False, visible=False ),
  153. ]
  154. columns.append(
  155. grids.MulticolFilterColumn(
  156. "Search",
  157. cols_to_filter=[ columns[0], columns[1] ],
  158. key="free-text-search", visible=False, filterable="standard" )
  159. )
  160. class PageSelectionGrid( ItemSelectionGrid ):
  161. """ Grid for selecting pages. """
  162. # Grid definition.
  163. title = "Saved Pages"
  164. model_class = model.Page
  165. columns = [
  166. grids.TextColumn( "Title", key="title", filterable="advanced" ),
  167. grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.PageTagAssociation, filterable="advanced"),
  168. grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
  169. # Columns that are valid for filtering but are not visible.
  170. grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ),
  171. grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False, visible=False ),
  172. ]
  173. columns.append(
  174. grids.MulticolFilterColumn(
  175. "Search",
  176. cols_to_filter=[ columns[0], columns[1] ],
  177. key="free-text-search", visible=False, filterable="standard" )
  178. )
  179. class VisualizationSelectionGrid( ItemSelectionGrid ):
  180. """ Grid for selecting visualizations. """
  181. # Grid definition.
  182. title = "Saved Visualizations"
  183. model_class = model.Visualization
  184. columns = [
  185. grids.TextColumn( "Title", key="title", filterable="advanced" ),
  186. grids.TextColumn( "Type", key="type" ),
  187. grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.VisualizationTagAssociation, filterable="advanced", grid_name="VisualizationListGrid" ),
  188. grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False ),
  189. grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
  190. ]
  191. columns.append(
  192. grids.MulticolFilterColumn(
  193. "Search",
  194. cols_to_filter=[ columns[0], columns[2] ],
  195. key="free-text-search", visible=False, filterable="standard" )
  196. )
  197. class _PageContentProcessor( _BaseHTMLProcessor ):
  198. """ Processes page content to produce HTML that is suitable for display. For now, processor renders embedded objects. """
  199. def __init__( self, trans, encoding, type, render_embed_html_fn ):
  200. _BaseHTMLProcessor.__init__( self, encoding, type)
  201. self.trans = trans
  202. self.ignore_content = False
  203. self.num_open_tags_for_ignore = 0
  204. self.render_embed_html_fn = render_embed_html_fn
  205. def unknown_starttag( self, tag, attrs ):
  206. """ Called for each start tag; attrs is a list of (attr, value) tuples. """
  207. # If ignoring content, just increment tag count and ignore.
  208. if self.ignore_content:
  209. self.num_open_tags_for_ignore += 1
  210. return
  211. # Not ignoring tag; look for embedded content.
  212. embedded_item = False
  213. for attribute in attrs:
  214. if ( attribute[0] == "class" ) and ( "embedded-item" in attribute[1].split(" ") ):
  215. embedded_item = True
  216. break
  217. # For embedded content, set ignore flag to ignore current content and add new content for embedded item.
  218. if embedded_item:
  219. # Set processing attributes to ignore content.
  220. self.ignore_content = True
  221. self.num_open_tags_for_ignore = 1
  222. # Insert content for embedded element.
  223. for attribute in attrs:
  224. name = attribute[0]
  225. if name == "id":
  226. # ID has form '<class_name>-<encoded_item_id>'
  227. item_class, item_id = attribute[1].split("-")
  228. embed_html = self.render_embed_html_fn( self.trans, item_class, item_id )
  229. self.pieces.append( embed_html )
  230. return
  231. # Default behavior: not ignoring and no embedded content.
  232. _BaseHTMLProcessor.unknown_starttag( self, tag, attrs )
  233. def handle_data( self, text ):
  234. """ Called for each block of plain text. """
  235. if self.ignore_content:
  236. return
  237. _BaseHTMLProcessor.handle_data( self, text )
  238. def unknown_endtag( self, tag ):
  239. """ Called for each end tag. """
  240. # If ignoring content, see if current tag is the end of content to ignore.
  241. if self.ignore_content:
  242. self.num_open_tags_for_ignore -= 1
  243. if self.num_open_tags_for_ignore == 0:
  244. # Done ignoring content.
  245. self.ignore_content = False
  246. return
  247. # Default behavior:
  248. _BaseHTMLProcessor.unknown_endtag( self, tag )
  249. class PageController( BaseUIController, Sharable, UsesAnnotations, UsesHistory,
  250. UsesStoredWorkflow, UsesHistoryDatasetAssociation, UsesVisualization, UsesItemRatings ):
  251. _page_list = PageListGrid()
  252. _all_published_list = PageAllPublishedGrid()
  253. _history_selection_grid = HistorySelectionGrid()
  254. _workflow_selection_grid = WorkflowSelectionGrid()
  255. _datasets_selection_grid = HistoryDatasetAssociationSelectionGrid()
  256. _page_selection_grid = PageSelectionGrid()
  257. _visualization_selection_grid = VisualizationSelectionGrid()
  258. @web.expose
  259. @web.require_login()
  260. def list( self, trans, *args, **kwargs ):
  261. """ List user's pages. """
  262. # Handle operation
  263. if 'operation' in kwargs and 'id' in kwargs:
  264. session = trans.sa_session
  265. operation = kwargs['operation'].lower()
  266. ids = util.listify( kwargs['id'] )
  267. for id in ids:
  268. item = session.query( model.Page ).get( trans.security.decode_id( id ) )
  269. if operation == "delete":
  270. item.deleted = True
  271. if operation == "share or publish":
  272. return self.sharing( trans, **kwargs )
  273. session.flush()
  274. # Build grid HTML.
  275. grid = self._page_list( trans, *args, **kwargs )
  276. # Build list of pages shared with user.
  277. shared_by_others = trans.sa_session \
  278. .query( model.PageUserShareAssociation ) \
  279. .filter_by( user=trans.get_user() ) \
  280. .join( model.Page.table ) \
  281. .filter( model.Page.deleted == False ) \
  282. .order_by( desc( model.Page.update_time ) ) \
  283. .all()
  284. # Render grid wrapped in panels
  285. return trans.fill_template( "page/index.mako", grid=grid, shared_by_others=shared_by_others )
  286. @web.expose
  287. def list_published( self, trans, *args, **kwargs ):
  288. grid = self._all_published_list( trans, *args, **kwargs )
  289. if 'async' in kwargs:
  290. return grid
  291. else:
  292. # Render grid wrapped in panels
  293. return trans.fill_template( "page/list_published.mako", grid=grid )
  294. @web.expose
  295. @web.require_login( "create pages" )
  296. def create( self, trans, page_title="", page_slug="", page_annotation="" ):
  297. """
  298. Create a new page
  299. """
  300. user = trans.get_user()
  301. page_title_err = page_slug_err = page_annotation_err = ""
  302. if trans.request.method == "POST":
  303. if not page_title:
  304. page_title_err = "Page name is required"
  305. elif not page_slug:
  306. page_slug_err = "Page id is required"
  307. elif not VALID_SLUG_RE.match( page_slug ):
  308. page_slug_err = "Page identifier must consist of only lowercase letters, numbers, and the '-' character"
  309. elif trans.sa_session.query( model.Page ).filter_by( user=user, slug=page_slug, deleted=False ).first():
  310. page_slug_err = "Page id must be unique"
  311. else:
  312. # Create the new stored page
  313. page = model.Page()
  314. page.title = page_title
  315. page.slug = page_slug
  316. page_annotation = sanitize_html( page_annotation, 'utf-8', 'text/html' )
  317. self.add_item_annotation( trans.sa_session, trans.get_user(), page, page_annotation )
  318. page.user = user
  319. # And the first (empty) page revision
  320. page_revision = model.PageRevision()
  321. page_revision.title = page_title
  322. page_revision.page = page
  323. page.latest_revision = page_revision
  324. page_revision.content = ""
  325. # Persist
  326. session = trans.sa_session
  327. session.add( page )
  328. session.flush()
  329. # Display the management page
  330. ## trans.set_message( "Page '%s' created" % page.title )
  331. return trans.response.send_redirect( web.url_for( action='list' ) )
  332. return trans.show_form(
  333. web.FormBuilder( web.url_for(), "Create new page", submit_text="Submit" )
  334. .add_text( "page_title", "Page title", value=page_title, error=page_title_err )
  335. .add_text( "page_slug", "Page identifier", value=page_slug, error=page_slug_err,
  336. help="""A unique identifier that will be used for
  337. public links to this page. A default is generated
  338. from the page title, but can be edited. This field
  339. must contain only lowercase letters, numbers, and
  340. the '-' character.""" )
  341. .add_text( "page_annotation", "Page annotation", value=page_annotation, error=page_annotation_err,
  342. help="A description of the page; annotation is shown alongside published pages."),
  343. template="page/create.mako" )
  344. @web.expose
  345. @web.require_login( "edit pages" )
  346. def edit( self, trans, id, page_title="", page_slug="", page_annotation="" ):
  347. """
  348. Edit a page's attributes.
  349. """
  350. encoded_id = id
  351. id = trans.security.decode_id( id )
  352. session = trans.sa_session
  353. page = session.query( model.Page ).get( id )
  354. user = trans.user
  355. assert page.user == user
  356. page_title_err = page_slug_err = page_annotation_err = ""
  357. if trans.request.method == "POST":
  358. if not page_title:
  359. page_title_err = "Page name is required"
  360. elif not page_slug:
  361. page_slug_err = "Page id is required"
  362. elif not VALID_SLUG_RE.match( page_slug ):
  363. page_slug_err = "Page identifier must consist of only lowercase letters, numbers, and the '-' character"
  364. elif page_slug != page.slug and trans.sa_session.query( model.Page ).filter_by( user=user, slug=page_slug, deleted=False ).first():
  365. page_slug_err = "Page id must be unique"
  366. elif not page_annotation:
  367. page_annotation_err = "Page annotation is required"
  368. else:
  369. page.title = page_title
  370. page.slug = page_slug
  371. page_annotation = sanitize_html( page_annotation, 'utf-8', 'text/html' )
  372. self.add_item_annotation( trans.sa_session, trans.get_user(), page, page_annotation )
  373. session.flush()
  374. # Redirect to page list.
  375. return trans.response.send_redirect( web.url_for( action='list' ) )
  376. else:
  377. page_title = page.title
  378. page_slug = page.slug
  379. page_annotation = self.get_item_annotation_str( trans.sa_session, trans.user, page )
  380. if not page_annotation:
  381. page_annotation = ""
  382. return trans.show_form(
  383. web.FormBuilder( web.url_for( id=encoded_id ), "Edit page attributes", submit_text="Submit" )
  384. .add_text( "page_title", "Page title", value=page_title, error=page_title_err )
  385. .add_text( "page_slug", "Page identifier", value=page_slug, error=page_slug_err,
  386. help="""A unique identifier that will be used for
  387. public links to this page. A default is generated
  388. from the page title, but can be edited. This field
  389. must contain only lowercase letters, numbers, and
  390. the '-' character.""" )
  391. .add_text( "page_annotation", "Page annotation", value=page_annotation, error=page_annotation_err,
  392. help="A description of the page; annotation is shown alongside published pages."),
  393. template="page/create.mako" )
  394. @web.expose
  395. @web.require_login( "edit pages" )
  396. def edit_content( self, trans, id ):
  397. """
  398. Render the main page editor interface.
  399. """
  400. id = trans.security.decode_id( id )
  401. page = trans.sa_session.query( model.Page ).get( id )
  402. assert page.user == trans.user
  403. return trans.fill_template( "page/editor.mako", page=page )
  404. @web.expose
  405. @web.require_login( "use Galaxy pages" )
  406. def sharing( self, trans, id, **kwargs ):
  407. """ Handle page sharing. """
  408. # Get session and page.
  409. session = trans.sa_session
  410. page = trans.sa_session.query( model.Page ).get( trans.security.decode_id( id ) )
  411. # Do operation on page.
  412. if 'make_accessible_via_link' in kwargs:
  413. self._make_item_accessible( trans.sa_session, page )
  414. elif 'make_accessible_and_publish' in kwargs:
  415. self._make_item_accessible( trans.sa_session, page )
  416. page.published = True
  417. elif 'publish' in kwargs:
  418. page.published = True
  419. elif 'disable_link_access' in kwargs:
  420. page.importable = False
  421. elif 'unpublish' in kwargs:
  422. page.published = False
  423. elif 'disable_link_access_and_unpublish' in kwargs:
  424. page.importable = page.published = False
  425. elif 'unshare_user' in kwargs:
  426. user = session.query( model.User ).get( trans.security.decode_id( kwargs['unshare_user' ] ) )
  427. if not user:
  428. error( "User not found for provided id" )
  429. association = session.query( model.PageUserShareAssociation ) \
  430. .filter_by( user=user, page=page ).one()
  431. session.delete( association )
  432. session.flush()
  433. return trans.fill_template( "/sharing_base.mako",
  434. item=page, use_panels=True )
  435. @web.expose
  436. @web.require_login( "use Galaxy pages" )
  437. def share( self, trans, id, email="", use_panels=False ):
  438. """ Handle sharing with an individual user. """
  439. msg = mtype = None
  440. page = trans.sa_session.query( model.Page ).get( trans.security.decode_id( id ) )
  441. if email:
  442. other = trans.sa_session.query( model.User ) \
  443. .filter( and_( model.User.table.c.email==email,
  444. model.User.table.c.deleted==False ) ) \
  445. .first()
  446. if not other:
  447. mtype = "error"
  448. msg = ( "User '%s' does not exist" % email )
  449. elif other == trans.get_user():
  450. mtype = "error"
  451. msg = ( "You cannot share a page with yourself" )
  452. elif trans.sa_session.query( model.PageUserShareAssociation ) \
  453. .filter_by( user=other, page=page ).count() > 0:
  454. mtype = "error"
  455. msg = ( "Page already shared with '%s'" % email )
  456. else:
  457. share = model.PageUserShareAssociation()
  458. share.page = page
  459. share.user = other
  460. session = trans.sa_session
  461. session.add( share )
  462. self.create_item_slug( session, page )
  463. session.flush()
  464. trans.set_message( "Page '%s' shared with user '%s'" % ( page.title, other.email ) )
  465. return trans.response.send_redirect( url_for( controller='page', action='sharing', id=id ) )
  466. return trans.fill_template( "/ind_share_base.mako",
  467. message = msg,
  468. messagetype = mtype,
  469. item=page,
  470. email=email,
  471. use_panels=use_panels )
  472. @web.expose
  473. @web.require_login()
  474. def save( self, trans, id, content, annotations ):
  475. id = trans.security.decode_id( id )
  476. page = trans.sa_session.query( model.Page ).get( id )
  477. assert page.user == trans.user
  478. # Sanitize content
  479. content = sanitize_html( content, 'utf-8', 'text/html' )
  480. # Add a new revision to the page with the provided content.
  481. page_revision = model.PageRevision()
  482. page_revision.title = page.title
  483. page_revision.page = page
  484. page.latest_revision = page_revision
  485. page_revision.content = content
  486. # Save annotations.
  487. annotations = from_json_string( annotations )
  488. for annotation_dict in annotations:
  489. item_id = trans.security.decode_id( annotation_dict[ 'item_id' ] )
  490. item_class = self.get_class( annotation_dict[ 'item_class' ] )
  491. item = trans.sa_session.query( item_class ).filter_by( id=item_id ).first()
  492. if not item:
  493. raise RuntimeError( "cannot find annotated item" )
  494. text = sanitize_html( annotation_dict[ 'text' ], 'utf-8', 'text/html' )
  495. # Add/update annotation.
  496. if item_id and item_class and text:
  497. # Get annotation association.
  498. annotation_assoc_class = eval( "model.%sAnnotationAssociation" % item_class.__name__ )
  499. annotation_assoc = trans.sa_session.query( annotation_assoc_class ).filter_by( user=trans.get_user() )
  500. if item_class == model.History.__class__:
  501. annotation_assoc = annotation_assoc.filter_by( history=item )
  502. elif item_class == model.HistoryDatasetAssociation.__class__:
  503. annotation_assoc = annotation_assoc.filter_by( hda=item )
  504. elif item_class == model.StoredWorkflow.__class__:
  505. annotation_assoc = annotation_assoc.filter_by( stored_workflow=item )
  506. elif item_class == model.WorkflowStep.__class__:
  507. annotation_assoc = annotation_assoc.filter_by( workflow_step=item )
  508. annotation_assoc = annotation_assoc.first()
  509. if not annotation_assoc:
  510. # Create association.
  511. annotation_assoc = annotation_assoc_class()
  512. item.annotations.append( annotation_assoc )
  513. annotation_assoc.user = trans.get_user()
  514. # Set annotation user text.
  515. annotation_assoc.annotation = text
  516. trans.sa_session.flush()
  517. @web.expose
  518. @web.require_login()
  519. def display( self, trans, id ):
  520. id = trans.security.decode_id( id )
  521. page = trans.sa_session.query( model.Page ).get( id )
  522. if not page:
  523. raise web.httpexceptions.HTTPNotFound()
  524. return self.display_by_username_and_slug( trans, page.user.username, page.slug )
  525. @web.expose
  526. def display_by_username_and_slug( self, trans, username, slug ):
  527. """ Display page based on a username and slug. """
  528. # Get page.
  529. session = trans.sa_session
  530. user = session.query( model.User ).filter_by( username=username ).first()
  531. page = trans.sa_session.query( model.Page ).filter_by( user=user, slug=slug, deleted=False ).first()
  532. if page is None:
  533. raise web.httpexceptions.HTTPNotFound()
  534. # Security check raises error if user cannot access page.
  535. self.security_check( trans, page, False, True)
  536. # Process page content.
  537. processor = _PageContentProcessor( trans, 'utf-8', 'text/html', self._get_embed_html )
  538. processor.feed( page.latest_revision.content )
  539. # Get rating data.
  540. user_item_rating = 0
  541. if trans.get_user():
  542. user_item_rating = self.get_user_item_rating( trans.sa_session, trans.get_user(), page )
  543. if user_item_rating:
  544. user_item_rating = user_item_rating.rating
  545. else:
  546. user_item_rating = 0
  547. ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, page )
  548. # Output is string, so convert to unicode for display.
  549. page_content = unicode( processor.output(), 'utf-8' )
  550. return trans.fill_template_mako( "page/display.mako", item=page, item_data=page_content,
  551. user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings,
  552. content_only=True )
  553. @web.expose
  554. @web.require_login( "use Galaxy pages" )
  555. def set_accessible_async( self, trans, id=None, accessible=False ):
  556. """ Set page's importable attribute and slug. """
  557. page = self.get_page( trans, id )
  558. # Only set if importable value would change; this prevents a change in the update_time unless attribute really changed.
  559. importable = accessible in ['True', 'true', 't', 'T'];
  560. if page.importable != importable:
  561. if importable:
  562. self._make_item_accessible( trans.sa_session, page )
  563. else:
  564. page.importable = importable
  565. trans.sa_session.flush()
  566. return
  567. @web.expose
  568. @web.require_login( "modify Galaxy items" )
  569. def set_slug_async( self, trans, id, new_slug ):
  570. page = self.get_page( trans, id )
  571. if page:
  572. page.slug = new_slug
  573. trans.sa_session.flush()
  574. return page.slug
  575. @web.expose
  576. @web.require_login( "rate items" )
  577. @web.json
  578. def rate_async( self, trans, id, rating ):
  579. """ Rate a page asynchronously and return updated community data. """
  580. page = self.get_page( trans, id, check_ownership=False, check_accessible=True )
  581. if not page:
  582. return trans.show_error_message( "The specified page does not exist." )
  583. # Rate page.
  584. page_rating = self.rate_item( trans.sa_session, trans.get_user(), page, rating )
  585. return self.get_ave_item_rating_data( trans.sa_session, page )
  586. @web.expose
  587. def get_embed_html_async( self, trans, id ):
  588. """ Returns HTML for embedding a workflow in a page. """
  589. # TODO: user should be able to embed any item he has access to. see display_by_username_and_slug for security code.
  590. page = self.get_page( trans, id )
  591. if page:
  592. return "Embedded Page '%s'" % page.title
  593. @web.expose
  594. @web.json
  595. @web.require_login( "use Galaxy pages" )
  596. def get_name_and_link_async( self, trans, id=None ):
  597. """ Returns page's name and link. """
  598. page = self.get_page( trans, id )
  599. if self.create_item_slug( trans.sa_session, page ):
  600. trans.sa_session.flush()
  601. return_dict = { "name" : page.title, "link" : url_for( action="display_by_username_and_slug", username=page.user.username, slug=page.slug ) }
  602. return return_dict
  603. @web.expose
  604. @web.require_login("select a history from saved histories")
  605. def list_histories_for_selection( self, trans, **kwargs ):
  606. """ Returns HTML that enables a user to select one or more histories. """
  607. # Render the list view
  608. return self._history_selection_grid( trans, **kwargs )
  609. @web.expose
  610. @web.require_login("select a workflow from saved workflows")
  611. def list_workflows_for_selection( self, trans, **kwargs ):
  612. """ Returns HTML that enables a user to select one or more workflows. """
  613. # Render the list view
  614. return self._workflow_selection_grid( trans, **kwargs )
  615. @web.expose
  616. @web.require_login("select a visualization from saved visualizations")
  617. def list_visualizations_for_selection( self, trans, **kwargs ):
  618. """ Returns HTML that enables a user to select one or more visualizations. """
  619. # Render the list view
  620. return self._visualization_selection_grid( trans, **kwargs )
  621. @web.expose
  622. @web.require_login("select a page from saved pages")
  623. def list_pages_for_selection( self, trans, **kwargs ):
  624. """ Returns HTML that enables a user to select one or more pages. """
  625. # Render the list view
  626. return self._page_selection_grid( trans, **kwargs )
  627. @web.expose
  628. @web.require_login("select a dataset from saved datasets")
  629. def list_datasets_for_selection( self, trans, **kwargs ):
  630. """ Returns HTML that enables a user to select one or more datasets. """
  631. # Render the list view
  632. return self._datasets_selection_grid( trans, **kwargs )
  633. @web.expose
  634. @web.require_login("get annotation table for history")
  635. def get_history_annotation_table( self, trans, id ):
  636. """ Returns HTML for an annotation table for a history. """
  637. history = self.get_history( trans, id, False, True )
  638. if history:
  639. datasets = self.get_history_datasets( trans, history )
  640. return trans.fill_template( "page/history_annotation_table.mako", history=history, datasets=datasets, show_deleted=False )
  641. @web.expose
  642. def get_editor_iframe( self, trans ):
  643. """ Returns the document for the page editor's iframe. """
  644. return trans.fill_template( "page/wymiframe.mako" )
  645. def get_page( self, trans, id, check_ownership=True, check_accessible=False ):
  646. """Get a page from the database by id."""
  647. # Load history from database
  648. id = trans.security.decode_id( id )
  649. page = trans.sa_session.query( model.Page ).get( id )
  650. if not page:
  651. err+msg( "Page not found" )
  652. else:
  653. return self.security_check( trans, page, check_ownership, check_accessible )
  654. def get_item( self, trans, id ):
  655. return self.get_page( trans, id )
  656. def _get_embed_html( self, trans, item_class, item_id ):
  657. """ Returns HTML for embedding an item in a page. """
  658. item_class = self.get_class( item_class )
  659. if item_class == model.History:
  660. history = self.get_history( trans, item_id, False, True )
  661. history.annotation = self.get_item_annotation_str( trans.sa_session, history.user, history )
  662. if history:
  663. datasets = self.get_history_datasets( trans, history )
  664. return trans.fill_template( "history/embed.mako", item=history, item_data=datasets )
  665. elif item_class == model.HistoryDatasetAssociation:
  666. dataset = self.get_dataset( trans, item_id, False, True )
  667. dataset.annotation = self.get_item_annotation_str( trans.sa_session, dataset.history.user, dataset )
  668. if dataset:
  669. data = self.get_data( dataset )
  670. return trans.fill_template( "dataset/embed.mako", item=dataset, item_data=data )
  671. elif item_class == model.StoredWorkflow:
  672. workflow = self.get_stored_workflow( trans, item_id, False, True )
  673. workflow.annotation = self.get_item_annotation_str( trans.sa_session, workflow.user, workflow )
  674. if workflow:
  675. self.get_stored_workflow_steps( trans, workflow )
  676. return trans.fill_template( "workflow/embed.mako", item=workflow, item_data=workflow.latest_workflow.steps )
  677. elif item_class == model.Visualization:
  678. visualization = self.get_visualization( trans, item_id, False, True )
  679. visualization.annotation = self.get_item_annotation_str( trans.sa_session, visualization.user, visualization )
  680. if visualization:
  681. return trans.fill_template( "visualization/embed.mako", item=visualization, item_data=None )
  682. elif item_class == model.Page:
  683. pass