mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Visualizations registry: refactor the relationship between plugins, the visualization controller, rendering, and rendering saved visualizations, minor fixes; Extend testing; Scatterplot: fix baseUrl resolution when used in own window
This commit is contained in:
@@ -20,7 +20,8 @@ var ScatterplotDisplay = Backbone.View.extend({
|
||||
var view = this,
|
||||
config = this.model.get( 'config' ),
|
||||
//TODO: very tied to datasets - should be generalized eventually
|
||||
xhr = jQuery.getJSON(((typeof parent.galaxy_config.root == 'undefined') ? '/' : parent.galaxy_config.root) + 'api/datasets/' + this.dataset.id, {
|
||||
baseUrl = ( window.parent && parent.galaxy_config )? parent.galaxy_config.root : '/',
|
||||
xhr = jQuery.getJSON( baseUrl + 'api/datasets/' + this.dataset.id, {
|
||||
data_type : 'raw_data',
|
||||
provider : 'dataset-column',
|
||||
limit : config.pagination.perPage,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -129,7 +129,7 @@ class VisualizationPlugin( pluginframework.Plugin, ServesStaticPluginMixin, Serv
|
||||
self.config = config
|
||||
|
||||
base_url = context.get( 'base_url', '' )
|
||||
self.base_url = '/'.join([ base_url, self.name ])
|
||||
self.base_url = '/'.join([ base_url, self.name ]) if base_url else self.name
|
||||
|
||||
self._set_up_static_plugin()
|
||||
|
||||
@@ -139,87 +139,79 @@ class VisualizationPlugin( pluginframework.Plugin, ServesStaticPluginMixin, Serv
|
||||
|
||||
self.resource_parser = resource_parser.ResourceParser( app )
|
||||
|
||||
def render( self, trans=None, visualization=None, config=None, embedded=None, **kwargs ):
|
||||
def render( self, trans=None, embedded=None, **kwargs ):
|
||||
"""
|
||||
Return the text of the rendered plugin webpage/fragment.
|
||||
Render and return the text of the non-saved plugin webpage/fragment.
|
||||
"""
|
||||
if visualization:
|
||||
return self._render_saved( visualization, config, trans=trans, embedded=embedded, **kwargs )
|
||||
config = {}
|
||||
context = self._default_context_vars( embedded=embedded, **kwargs )
|
||||
return self._render( config, trans=trans, embedded=embedded, context=context, **kwargs )
|
||||
|
||||
# get the config for passing to the template from the kwargs dict, parsed using the plugin's params setting
|
||||
config_from_kwargs = self._query_dict_to_config( trans, kwargs )
|
||||
config = utils.OpenObject( **config_from_kwargs )
|
||||
|
||||
# further parse config to resources (models, etc.) used in template based on registry config
|
||||
resources = self._query_dict_to_resources( trans, config )
|
||||
|
||||
# if a saved visualization, pass in the encoded visualization id or None if a new render
|
||||
encoded_visualization_id = None
|
||||
|
||||
visualization_display_name = self.config[ 'name' ]
|
||||
title = kwargs.get( 'title', None )
|
||||
|
||||
return self._fill_template( trans,
|
||||
|
||||
visualization_name=self.name,
|
||||
visualization_display_name=visualization_display_name,
|
||||
title=title,
|
||||
def render_saved( self, visualization, config, trans=None, embedded=None, **kwargs ):
|
||||
"""
|
||||
Render and return the text of the plugin webpage/fragment using the
|
||||
config/data of a saved visualization.
|
||||
"""
|
||||
context = self._default_context_vars( embedded=embedded, **kwargs )
|
||||
# update any values that were loaded from the Visualization
|
||||
context.update( dict(
|
||||
title=visualization.latest_revision.title,
|
||||
saved_visualization=visualization,
|
||||
visualization_id=encoded_visualization_id,
|
||||
visualization_id=trans.security.encode_id( visualization.id ),
|
||||
))
|
||||
return self._render( config, trans=trans, embedded=embedded, context=context, **kwargs )
|
||||
|
||||
embedded=embedded,
|
||||
# ---- non-public
|
||||
def _default_context_vars( self, **kwargs ):
|
||||
"""
|
||||
Meta variables passed to the template/renderer to describe the visualization
|
||||
being rendered.
|
||||
|
||||
These are the defaults used when a saved visualization isn't present to
|
||||
provide them.
|
||||
"""
|
||||
return dict(
|
||||
visualization_name=self.name,
|
||||
visualization_display_name=self.config[ 'name' ],
|
||||
title=kwargs.get( 'title', None ),
|
||||
saved_visualization=None,
|
||||
visualization_id=None,
|
||||
# NOTE: passing *unparsed* kwargs as query
|
||||
query=kwargs,
|
||||
# NOTE: vars is a dictionary for shared data in the template
|
||||
# this feels hacky to me but it's what mako recommends:
|
||||
# http://docs.makotemplates.org/en/latest/runtime.html
|
||||
vars={},
|
||||
config=config,
|
||||
**resources
|
||||
)
|
||||
|
||||
def _render_saved( self, visualization, config, trans=None, embedded=None, **kwargs ):
|
||||
def _render( self, config, context, trans=None, embedded=None, **kwargs ):
|
||||
"""
|
||||
Return the text of the saved visualization.
|
||||
Build/fetch the variables needed to render the visualization and call the renderer.
|
||||
"""
|
||||
config = config or {}
|
||||
template_args = {}
|
||||
|
||||
# get the config for passing to the template from the kwargs dict, parsed using the plugin's params setting
|
||||
config = config or {}
|
||||
config_from_kwargs = self._query_dict_to_config( trans, kwargs )
|
||||
config.update( config_from_kwargs )
|
||||
config = utils.OpenObject( **config )
|
||||
template_args[ 'config' ] = config
|
||||
|
||||
# further parse config to resources (models, etc.) used in template based on registry config
|
||||
resources = self._query_dict_to_resources( trans, config )
|
||||
template_args.update( resources )
|
||||
|
||||
# if a saved visualization, pass in the encoded visualization id or None if a new render
|
||||
encoded_visualization_id = trans.security.encode_id( visualization.id )
|
||||
# add any extra variables dealing with the visualization itself, saved visualizations, etc.
|
||||
template_args.update( context )
|
||||
|
||||
visualization_display_name = self.config[ 'name' ]
|
||||
title = visualization.latest_revision.title
|
||||
template_args.update( embedded=embedded )
|
||||
return self._fill_template( trans, **template_args )
|
||||
|
||||
return self._fill_template( trans,
|
||||
visualization_name=self.name,
|
||||
visualization_display_name=visualization_display_name,
|
||||
title=title,
|
||||
saved_visualization=visualization,
|
||||
visualization_id=encoded_visualization_id,
|
||||
embedded=embedded,
|
||||
# NOTE: passing *unparsed* kwargs as query
|
||||
query=kwargs,
|
||||
# NOTE: vars is a dictionary for shared data in the template
|
||||
# this feels hacky to me but it's what mako recommends:
|
||||
# http://docs.makotemplates.org/en/latest/runtime.html
|
||||
vars={},
|
||||
config=config,
|
||||
**resources
|
||||
)
|
||||
|
||||
# ---------------- getting resources for visualization templates from link query strings
|
||||
def _fill_template( self, trans, **kwargs ):
|
||||
# NOTE: (mako specific) vars is a dictionary for shared data in the template
|
||||
# this feels hacky to me but it's what mako recommends:
|
||||
# http://docs.makotemplates.org/en/latest/runtime.html
|
||||
kwargs.update( vars={} )
|
||||
template_filename = self.config[ 'entry_point' ][ 'file' ]
|
||||
return trans.fill_template( template_filename, template_lookup=self.template_lookup, **kwargs )
|
||||
|
||||
# ---------------- getting resources for visualization templates from link query strings
|
||||
def _get_resource_params_and_modifiers( self ):
|
||||
"""
|
||||
Get params and modifiers for the given visualization as a 2-tuple.
|
||||
|
||||
@@ -8,6 +8,7 @@ import os
|
||||
import weakref
|
||||
|
||||
from galaxy.web import url_for
|
||||
import galaxy.exceptions
|
||||
|
||||
from galaxy.web.base import pluginframework
|
||||
from galaxy.visualization.plugins import config_parser
|
||||
@@ -114,6 +115,14 @@ class VisualizationsRegistry( pluginframework.PageServingPluginManager ):
|
||||
))
|
||||
return plugin
|
||||
|
||||
def get_plugin( self, key ):
|
||||
"""
|
||||
Wrap to throw error if plugin not in registry.
|
||||
"""
|
||||
if key not in self.plugins:
|
||||
raise galaxy.exceptions.ObjectNotFound( 'Unknown or invalid visualization: ' + key )
|
||||
return self.plugins[ key ]
|
||||
|
||||
# -- building links to visualizations from objects --
|
||||
def get_visualizations( self, trans, target_object ):
|
||||
"""
|
||||
|
||||
@@ -716,38 +716,14 @@ class VisualizationController( BaseUIController, SharableMixin, UsesVisualizatio
|
||||
help="A description of the visualization; annotation is shown alongside published visualizations."),
|
||||
template="visualization/create.mako" )
|
||||
|
||||
#
|
||||
# Visualizations.
|
||||
#
|
||||
# ------------------------- registry.
|
||||
@web.expose
|
||||
@web.require_login( "use Galaxy visualizations", use_panels=True )
|
||||
def saved( self, trans, id=None, revision=None, type=None, config=None, title=None, **kwargs ):
|
||||
"""
|
||||
"""
|
||||
DEFAULT_VISUALIZATION_NAME = 'Unnamed Visualization'
|
||||
|
||||
# post to saved in order to save a visualization
|
||||
# TODO: re-route this one to clear up signature
|
||||
if trans.request.method == 'POST':
|
||||
if type is None or config is None:
|
||||
return HTTPBadRequest( 'A visualization type and config are required to save a visualization' )
|
||||
if isinstance( config, basestring ):
|
||||
config = loads( config )
|
||||
title = title or DEFAULT_VISUALIZATION_NAME
|
||||
|
||||
# TODO: allow saving to (updating) a specific revision - should be part of UsesVisualization
|
||||
# TODO: would be easier if this returned the visualization directly
|
||||
# check security if posting to existing visualization
|
||||
if id is not None:
|
||||
visualization = self.get_visualization( trans, id, check_ownership=True, check_accessible=False )
|
||||
# ??: on not owner: error raised, but not returned (status = 200)
|
||||
|
||||
# TODO: there's no security check in save visualization (if passed an id)
|
||||
returned = self.save_visualization( trans, config, type, id, title )
|
||||
|
||||
# redirect to GET to prevent annoying 'Do you want to post again?' dialog on page reload
|
||||
render_url = web.url_for( controller='visualization', action='saved', id=returned.get( 'vis_id' ) )
|
||||
return trans.response.send_redirect( render_url )
|
||||
self._POST_to_saved( trans, id=id, revision=revision, type=type, config=config, title=title, **kwargs )
|
||||
|
||||
if id is None:
|
||||
return HTTPBadRequest( 'A valid visualization id is required to load a visualization' )
|
||||
@@ -760,11 +736,15 @@ class VisualizationController( BaseUIController, SharableMixin, UsesVisualizatio
|
||||
# re-add title to kwargs for passing to render
|
||||
if title:
|
||||
kwargs[ 'title' ] = title
|
||||
return self.render( trans, visualization.type, visualization, config=config, **kwargs )
|
||||
plugin = self._get_plugin_from_registry( trans, visualization.type )
|
||||
try:
|
||||
return plugin.render_saved( visualization, config, trans=trans, **kwargs )
|
||||
except Exception, exception:
|
||||
self._handle_plugin_error( trans, visualization.type, exception )
|
||||
|
||||
@web.expose
|
||||
@web.require_login( "use Galaxy visualizations", use_panels=True )
|
||||
def render( self, trans, visualization_name, visualization=None, config=None, embedded=None, **kwargs ):
|
||||
def render( self, trans, visualization_name, embedded=None, **kwargs ):
|
||||
"""
|
||||
Render the appropriate visualization template, parsing the `kwargs`
|
||||
into appropriate variables and resources (such as ORM models)
|
||||
@@ -772,32 +752,66 @@ class VisualizationController( BaseUIController, SharableMixin, UsesVisualizatio
|
||||
|
||||
URL: /visualization/show/{visualization_name}
|
||||
"""
|
||||
config = config or {}
|
||||
|
||||
# validate name vs. registry
|
||||
registry = trans.app.visualizations_registry
|
||||
if not registry:
|
||||
raise HTTPNotFound( 'No visualization registry (possibly disabled in galaxy.ini)' )
|
||||
if visualization_name not in registry.plugins:
|
||||
raise HTTPNotFound( 'Unknown or invalid visualization: ' + visualization_name )
|
||||
plugin = registry.plugins[ visualization_name ]
|
||||
|
||||
returned = None
|
||||
plugin = self._get_plugin_from_registry( trans, visualization_name )
|
||||
try:
|
||||
returned = plugin.render( controller=self, trans=trans,
|
||||
visualization=visualization, config=config, embedded=embedded, **kwargs )
|
||||
|
||||
return plugin.render( trans=trans, embedded=embedded, **kwargs )
|
||||
except Exception, exception:
|
||||
log.exception( 'error rendering visualization (%s): %s', visualization_name, str( exception ) )
|
||||
if trans.debug:
|
||||
raise
|
||||
returned = trans.show_error_message(
|
||||
"There was an error rendering the visualization. " +
|
||||
"Contact your Galaxy administrator if the problem persists." +
|
||||
"<br/>Details: " + str( exception ), use_panels=False )
|
||||
self._handle_plugin_error( trans, visualization_name, exception )
|
||||
|
||||
return returned
|
||||
def _POST_to_saved( self, trans, id=None, revision=None, type=None, config=None, title=None, **kwargs ):
|
||||
"""
|
||||
Save the visualiztion info (revision, type, config, title, etc.) to
|
||||
the Visualization at `id` or to a new Visualization if `id` is None.
|
||||
|
||||
Uses POST/redirect/GET after a successful save, redirecting to GET.
|
||||
"""
|
||||
DEFAULT_VISUALIZATION_NAME = 'Unnamed Visualization'
|
||||
|
||||
# post to saved in order to save a visualization
|
||||
if type is None or config is None:
|
||||
return HTTPBadRequest( 'A visualization type and config are required to save a visualization' )
|
||||
if isinstance( config, basestring ):
|
||||
config = loads( config )
|
||||
title = title or DEFAULT_VISUALIZATION_NAME
|
||||
|
||||
# TODO: allow saving to (updating) a specific revision - should be part of UsesVisualization
|
||||
# TODO: would be easier if this returned the visualization directly
|
||||
# check security if posting to existing visualization
|
||||
if id is not None:
|
||||
self.get_visualization( trans, id, check_ownership=True, check_accessible=False )
|
||||
# ??: on not owner: error raised, but not returned (status = 200)
|
||||
# TODO: there's no security check in save visualization (if passed an id)
|
||||
returned = self.save_visualization( trans, config, type, id, title )
|
||||
|
||||
# redirect to GET to prevent annoying 'Do you want to post again?' dialog on page reload
|
||||
render_url = web.url_for( controller='visualization', action='saved', id=returned.get( 'vis_id' ) )
|
||||
return trans.response.send_redirect( render_url )
|
||||
|
||||
def _get_plugin_from_registry( self, trans, visualization_name ):
|
||||
"""
|
||||
Get the named plugin from the registry.
|
||||
:raises HTTPNotFound: if registry has been turned off in config.
|
||||
:raises HTTPNotFound: if visualization_name isn't a registered plugin.
|
||||
"""
|
||||
if not trans.app.visualizations_registry:
|
||||
raise HTTPNotFound( 'No visualization registry (possibly disabled in galaxy.ini)' )
|
||||
return trans.app.visualizations_registry.get_plugin( visualization_name )
|
||||
|
||||
def _handle_plugin_error( self, trans, visualization_name, exception ):
|
||||
"""
|
||||
Log, raise if debugging; log and show html message if not.
|
||||
"""
|
||||
log.exception( 'error rendering visualization (%s): %s', visualization_name, str( exception ) )
|
||||
if trans.debug:
|
||||
raise
|
||||
return trans.show_error_message(
|
||||
"There was an error rendering the visualization. " +
|
||||
"Contact your Galaxy administrator if the problem persists." +
|
||||
"<br/>Details: " + str( exception ), use_panels=False )
|
||||
|
||||
#
|
||||
# Visualizations.
|
||||
#
|
||||
@web.expose
|
||||
@web.require_login()
|
||||
def trackster(self, trans, **kwargs):
|
||||
@@ -833,7 +847,7 @@ class VisualizationController( BaseUIController, SharableMixin, UsesVisualizatio
|
||||
app['viz_config'] = self.get_visualization_config( trans, vis )
|
||||
|
||||
# backup id
|
||||
app['id'] = id;
|
||||
app['id'] = id
|
||||
|
||||
# add dataset id
|
||||
app['add_dataset'] = new_dataset_id
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
"""
|
||||
import os
|
||||
import imp
|
||||
import unittest
|
||||
|
||||
test_utils = imp.load_source( 'test_utils',
|
||||
os.path.join( os.path.dirname( __file__), '..', 'unittest_utils', 'utility.py' ) )
|
||||
import galaxy_mock
|
||||
|
||||
from galaxy.visualization.plugins import plugin as vis_plugin
|
||||
from galaxy.visualization.plugins import resource_parser
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
glx_dir = test_utils.get_galaxy_root()
|
||||
template_cache_dir = os.path.join( glx_dir, 'database', 'compiled_templates' )
|
||||
addtional_templates_dir = os.path.join( glx_dir, 'config', 'plugins', 'visualizations', 'common', 'templates' )
|
||||
vis_reg_path = 'config/plugins/visualizations'
|
||||
|
||||
config1 = """\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE visualization SYSTEM "../../visualization.dtd">
|
||||
<visualization name="scatterplot">
|
||||
<data_sources>
|
||||
<data_source>
|
||||
<model_class>HistoryDatasetAssociation</model_class>
|
||||
<test type="isinstance" test_attr="datatype" result_type="datatype">tabular.Tabular</test>
|
||||
<to_param param_attr="id">dataset_id</to_param>
|
||||
</data_source>
|
||||
</data_sources>
|
||||
<params>
|
||||
<param type="dataset" var_name_in_template="hda" required="true">dataset_id</param>
|
||||
</params>
|
||||
<template>scatterplot.mako</template>
|
||||
</visualization>
|
||||
"""
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class VisualizationsPlugin_TestCase( unittest.TestCase ):
|
||||
|
||||
def test_default_init( self ):
|
||||
"""
|
||||
"""
|
||||
vis_dir = galaxy_mock.MockDir({
|
||||
'config' : {
|
||||
'vis1.xml' : ''
|
||||
},
|
||||
'static' : {},
|
||||
'templates' : {},
|
||||
})
|
||||
config = dict()
|
||||
plugin = vis_plugin.VisualizationPlugin( galaxy_mock.MockApp(), vis_dir.root_path,
|
||||
'myvis', config )
|
||||
self.assertEqual( plugin.name, 'myvis' )
|
||||
self.assertEqual( plugin.path, vis_dir.root_path )
|
||||
self.assertEqual( plugin.config, {} )
|
||||
self.assertEqual( plugin.base_url, 'myvis' )
|
||||
# static
|
||||
self.assertTrue( plugin.serves_static )
|
||||
self.assertEqual( plugin.static_path, vis_dir.root_path + '/static' )
|
||||
self.assertEqual( plugin.static_url, 'myvis/static' )
|
||||
# template
|
||||
self.assertTrue( plugin.serves_templates )
|
||||
self.assertEqual( plugin.template_path, vis_dir.root_path + '/templates' )
|
||||
self.assertEqual( plugin.template_lookup.__class__.__name__, 'TemplateLookup' )
|
||||
# resource parser
|
||||
self.assertIsInstance( plugin.resource_parser, resource_parser.ResourceParser )
|
||||
|
||||
def test_init_with_context( self ):
|
||||
"""
|
||||
"""
|
||||
vis_dir = galaxy_mock.MockDir({
|
||||
'config' : {
|
||||
'vis1.xml' : ''
|
||||
},
|
||||
'static' : {},
|
||||
'templates' : {},
|
||||
})
|
||||
config = dict()
|
||||
context = dict(
|
||||
base_url='u/wot/m8',
|
||||
template_cache_dir='template_cache',
|
||||
additional_template_paths=[ 'one' ]
|
||||
)
|
||||
plugin = vis_plugin.VisualizationPlugin( galaxy_mock.MockApp(), vis_dir.root_path,
|
||||
'myvis', config, context=context )
|
||||
self.assertEqual( plugin.base_url, 'u/wot/m8/myvis' )
|
||||
# static
|
||||
self.assertEqual( plugin.static_url, 'u/wot/m8/myvis/static' )
|
||||
# template
|
||||
self.assertEqual( plugin.template_lookup.__class__.__name__, 'TemplateLookup' )
|
||||
|
||||
def test_init_without_static_or_templates( self ):
|
||||
"""
|
||||
"""
|
||||
vis_dir = galaxy_mock.MockDir({
|
||||
'config' : {
|
||||
'vis1.xml' : ''
|
||||
}
|
||||
})
|
||||
plugin = vis_plugin.VisualizationPlugin( galaxy_mock.MockApp(), vis_dir.root_path,
|
||||
'myvis', dict() )
|
||||
self.assertFalse( plugin.serves_static )
|
||||
self.assertFalse( plugin.serves_templates )
|
||||
# not sure what this would do, but...
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# TODO: config parser tests (in separate file)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user