diff --git a/.hgignore b/.hgignore
index e47109d73bc..bdcb4825403 100644
--- a/.hgignore
+++ b/.hgignore
@@ -60,7 +60,7 @@ shed_tool_data_table_conf.xml
job_conf.xml
data_manager_conf.xml
shed_data_manager_conf.xml
-config/visualizations/*.xml
+config/*
static/welcome.html.*
static/welcome.html
diff --git a/config/plugins/visualizations/README.txt b/config/plugins/visualizations/README.txt
new file mode 100644
index 00000000000..8f4a4c964e7
--- /dev/null
+++ b/config/plugins/visualizations/README.txt
@@ -0,0 +1,34 @@
+Custom visualization plugins
+----------------------------
+
+Visualizations can be added to your Galaxy instance by creating
+sub-directories, templates, and static files here.
+
+Properly configured and written visualizations will be accessible to
+the user when they click the 'visualizations' icon for a dataset
+in their history panel.
+
+The framework must be enabled in your 'universe_wsgi.ini' file by
+uncommenting (and having a valid path for) the
+'visualizations_plugin_directory' entry.
+
+For more information, see http://wiki.galaxyproject.org/VisualizationsRegistry
+
+
+Sub-directory structure
+-----------------------
+
+In general, sub-directories should follow the pattern:
+
+ my_visualization/
+ config/
+ my_visualization.xml
+ static/
+ ... any static files the visualization needs (if any)
+ templates/
+ ... any Mako templates the visualization needs
+
+The XML config file for a visualization plugin can be validated on the command
+line using (from your plugin directory):
+
+ xmllint my_visualization/config/my_visualization.xml --valid --noout
diff --git a/config/visualizations/visualization.dtd b/config/plugins/visualizations/visualization.dtd
similarity index 99%
rename from config/visualizations/visualization.dtd
rename to config/plugins/visualizations/visualization.dtd
index 75722998007..d874f6fd77a 100644
--- a/config/visualizations/visualization.dtd
+++ b/config/plugins/visualizations/visualization.dtd
@@ -1,5 +1,3 @@
-
-
diff --git a/config/visualizations/circster.xml.sample b/config/visualizations/circster.xml.sample
deleted file mode 100644
index cf15d7b66b1..00000000000
--- a/config/visualizations/circster.xml.sample
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
- HistoryDatasetAssociation
- data.Data
- dataset_id
- hda_ldda
-
-
- LibraryDatasetDatasetAssociation
- data.Data
- dataset_id
- hda_ldda
-
-
-
- id
- dataset_id
- hda_ldda
- dbkey
-
- circster.mako
- _top
-
diff --git a/config/visualizations/phyloviz.xml.sample b/config/visualizations/phyloviz.xml.sample
deleted file mode 100644
index a2a879ef800..00000000000
--- a/config/visualizations/phyloviz.xml.sample
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
- HistoryDatasetAssociation
- data.Newick
- data.Nexus
- dataset_id
-
-
-
- dataset_id
- tree_index
-
- phyloviz.mako
- _top
-
diff --git a/config/visualizations/scatterplot.xml.sample b/config/visualizations/scatterplot.xml.sample
deleted file mode 100644
index 3ab023cb5bc..00000000000
--- a/config/visualizations/scatterplot.xml.sample
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
- HistoryDatasetAssociation
- tabular.Tabular
- dataset_id
-
-
-
- dataset_id
-
- scatterplot.mako
-
diff --git a/config/visualizations/sweepster.xml.sample b/config/visualizations/sweepster.xml.sample
deleted file mode 100644
index 9dd19178756..00000000000
--- a/config/visualizations/sweepster.xml.sample
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
- HistoryDatasetAssociation
- data.Data
- dataset_id
- hda_ldda
-
-
- LibraryDatasetDatasetAssociation
- data.Data
- dataset_id
- hda_ldda
-
-
-
- visualization
- dataset_id
- hda_ldda
-
- sweepster.mako
- _top
-
diff --git a/config/visualizations/trackster.xml.sample b/config/visualizations/trackster.xml.sample
deleted file mode 100644
index 813fa97fbb2..00000000000
--- a/config/visualizations/trackster.xml.sample
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
- HistoryDatasetAssociation
- data.Data
- dataset_id
- hda_ldda
- dbkey
-
-
- LibraryDatasetDatasetAssociation
- data.Data
- dataset_id
- hda_ldda
-
-
-
- id
- dataset_id
- genome_region
- dbkey
-
- tracks
- browser.mako
- _top
-
diff --git a/lib/galaxy/app.py b/lib/galaxy/app.py
index da6b2a8318b..904750b5436 100644
--- a/lib/galaxy/app.py
+++ b/lib/galaxy/app.py
@@ -123,10 +123,8 @@ class UniverseApplication( object ):
# Load genome indexer tool.
load_genome_index_tools( self.toolbox )
# visualizations registry: associates resources with visualizations, controls how to render
- self.visualizations_registry = None
- if self.config.visualizations_config_directory:
- self.visualizations_registry = VisualizationsRegistry( self.config.root,
- self.config.visualizations_config_directory )
+ self.visualizations_registry = VisualizationsRegistry.from_config(
+ self.config.visualizations_plugins_directory, self.config )
# Load security policy.
self.security_agent = self.model.security_agent
self.host_security_agent = galaxy.security.HostAgent( model=self.security_agent.model, permitted_actions=self.security_agent.permitted_actions )
diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py
index 687f2ed2f6b..acb31da39da 100644
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -291,8 +291,10 @@ class Configuration( object ):
self.fluent_log = string_as_bool( kwargs.get( 'fluent_log', False ) )
self.fluent_host = kwargs.get( 'fluent_host', 'localhost' )
self.fluent_port = int( kwargs.get( 'fluent_port', 24224 ) )
- # visualization registries config directory
- self.visualizations_config_directory = kwargs.get( 'visualizations_config_directory', None )
+ # PLUGINS:
+ self.plugin_frameworks = []
+ # visualization framework
+ self.visualizations_plugins_directory = kwargs.get( 'visualizations_plugins_directory', None )
@property
def sentry_dsn_public( self ):
diff --git a/lib/galaxy/visualization/registry.py b/lib/galaxy/visualization/registry.py
index 5e0b8593f4d..188f24cd586 100644
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -12,6 +12,8 @@ from galaxy import util
import galaxy.model
from galaxy.web import url_for
+from galaxy.web.base import pluginframework
+
import logging
log = logging.getLogger( __name__ )
@@ -28,17 +30,16 @@ some confused vocabulary in docs, var names
tests:
anding, grouping, not
has_dataprovider
+ user is admin
data_sources:
lists of
add description element to visualization.
-TESTS to add:
- has dataprovider
- user is admin
+user_pref for ordering/ex/inclusion of particular visualizations
"""
# ------------------------------------------------------------------- the registry
-class VisualizationsRegistry( object ):
+class VisualizationsRegistry( pluginframework.PluginFramework ):
"""
Main responsibilities are:
- testing if an object has a visualization that can be applied to it
@@ -47,6 +48,7 @@ class VisualizationsRegistry( object ):
- validating and parsing params into resources (based on a context)
used in the visualization template
"""
+ #: any built in visualizations that have their own render method in ctrls/visualization
# these should be handled somewhat differently - and be passed onto their resp. methods in ctrl.visualization
#TODO: change/remove if/when they can be updated to use this system
BUILT_IN_VISUALIZATIONS = [
@@ -55,57 +57,37 @@ class VisualizationsRegistry( object ):
'sweepster',
'phyloviz'
]
- # where to search for visualiztion templates (relative to templates/webapps/galaxy)
+ #: where to search for visualiztion templates (relative to templates/webapps/galaxy)
# this can be overridden individually in the config entries
TEMPLATE_ROOT = 'visualization'
+ #: directories under plugin_directory that aren't plugins
+ non_plugin_directories = [ 'bler' ]
def __str__( self ):
- listings_keys_str = ','.join( self.listings.keys() ) if self.listings else ''
- return 'VisualizationsRegistry(%s)' %( listings_keys_str )
+ return 'VisualizationsRegistry(%s)' %( self.plugin_directory )
- def __init__( self, galaxy_root, configuration_filepath ):
- # load the registry from the xml files located in configuration_filepath using the given parser
- configuration_filepath = os.path.join( galaxy_root, configuration_filepath )
- self.configuration_filepath = self.check_conf_filepath( configuration_filepath )
- self.move_sample_conf_files()
- self.load()
+ def __init__( self, registry_filepath, template_cache_dir ):
+ super( VisualizationsRegistry, self ).__init__( registry_filepath, template_cache_dir )
# what to use to parse query strings into resources/vars for the template
self.resource_parser = ResourceParser()
+ log.debug( '%s loaded', str( self ) )
- def check_conf_filepath( self, configuration_filepath ):
+ def load_configuration( self ):
"""
- Checks for the existence of the given filepath.
- :param configurarion_filepath: full filepath to the visualization config directory
- :raises IOError: if the given directory doesn't exist
- """
- if not os.path.exists( configuration_filepath ):
- raise IOError( 'visualization configuration directory (%s) not found' %( configuration_filepath ) )
- return configuration_filepath
+ Builds the registry by parsing the `config/*.xml` files for every plugin
+ in ``get_plugin_directories`` and stores the results in ``self.listings``.
- def move_sample_conf_files( self ):
+ ..note::
+ This could be used to re-load a new configuration without restarting
+ the instance.
"""
- Copies any `*.xml.sample` files in `configuration_filepath` to
- `.xml` files of the same names if no file with that name already exists.
+ try:
+ self.listings = VisualizationsConfigParser.parse( self.get_plugin_directories() )
- :returns: a list of the files moved
- """
- files_moved = []
- for sample_file in glob.glob( os.path.join( self.configuration_filepath, '*.sample' ) ):
- new_name = os.path.splitext( sample_file )[0]
- if not os.path.exists( new_name ):
- shutil.copy2( sample_file, new_name )
- files_moved.append( new_name )
-
- def load( self ):
- """
- Builds the registry by parsing the xml in `self.configuration_filepath`
- and stores the results in `self.listings`.
-
- Provided as separate method from `__init__` in order to re-load a
- new configuration without restarting the instance.
- """
- self.listings = VisualizationsConfigParser.parse( self.configuration_filepath )
+ except Exception, exc:
+ log.exception( 'Error parsing visualizations plugins %s', self.plugin_directory )
+ raise
def get_visualization( self, trans, visualization_name, target_object ):
"""
@@ -283,11 +265,11 @@ class VisualizationsConfigParser( object ):
VALID_RENDER_LOCATIONS = [ 'galaxy_main', '_top', '_blank' ]
@classmethod
- def parse( cls, config_dir, debug=True ):
+ def parse( cls, plugin_directories, debug=False ):
"""
Static class interface.
"""
- return cls( debug ).parse_files( config_dir )
+ return cls( debug ).parse_plugins( plugin_directories )
def __init__( self, debug=False ):
self.debug = debug
@@ -297,33 +279,45 @@ class VisualizationsConfigParser( object ):
self.param_parser = ParamParser()
self.param_modifier_parser = ParamModifierParser()
- def parse_files( self, config_dir ):
+ def parse_plugins( self, plugin_directories ):
"""
- Parse each XML file in `config_dir` for visualizations config data.
+ Parses the config files for each plugin sub-dir in `base_path`.
+
+ :param plugin_directories: a list of paths to enabled plugins.
- If an error occurs while parsing a visualizations entry, it is skipped.
:returns: registry data in dictionary form
"""
returned = {}
- try:
- for xml_filepath in glob.glob( os.path.join( config_dir, '*.xml' ) ):
- try:
- visualization_name, visualization = self.parse_file( xml_filepath )
- # skip vis' with parsing errors - don't shutdown the startup
- except ParsingException, parse_exc:
- log.error( 'Skipped visualization config "%s" due to parsing errors: %s',
- xml_filepath, str( parse_exc ), exc_info=self.debug )
+ for plugin_path in plugin_directories:
+ returned.update( self.parse_plugin( plugin_path ) )
+ return returned
- if visualization:
- returned[ visualization_name ] = visualization
- log.debug( 'Visualization config loaded for: %s', visualization_name )
+ def parse_plugin( self, plugin_path ):
+ """
+ Parses any XML files in ``/config``.
- except Exception, exc:
- log.error( 'Error parsing visualizations configuration directory %s: %s',
- config_dir, str( exc ), exc_info=( not self.debug ) )
- #TODO: change when this framework is on by default
- if self.debug:
- raise
+ If an error occurs while parsing a visualizations entry, it is skipped.
+ :returns: registry data in dictionary form
+ ..note::
+ assumes config files are in a 'config' sub-dir of each plugin
+ """
+ returned = {}
+
+ plugin_config_path = os.path.join( plugin_path, 'config' )
+ if not os.path.isdir( plugin_config_path ):
+ return returned
+
+ for xml_filepath in glob.glob( os.path.join( plugin_config_path, '*.xml' ) ):
+ try:
+ visualization_name, visualization = self.parse_file( xml_filepath )
+ # skip vis' with parsing errors - don't shutdown the startup
+ except ParsingException, parse_exc:
+ log.error( 'Skipped visualization config "%s" due to parsing errors: %s',
+ xml_filepath, str( parse_exc ), exc_info=self.debug )
+
+ if visualization:
+ returned[ visualization_name ] = visualization
+ log.debug( 'Visualization config loaded for: %s', visualization_name )
return returned
diff --git a/lib/galaxy/web/base/pluginframework.py b/lib/galaxy/web/base/pluginframework.py
new file mode 100644
index 00000000000..0ead96e23fc
--- /dev/null
+++ b/lib/galaxy/web/base/pluginframework.py
@@ -0,0 +1,216 @@
+"""
+Base class for plugins - frameworks or systems that may:
+ * serve static content
+ * serve templated html
+ * have some configuration at startup
+"""
+
+import os.path
+import glob
+import sys
+
+import pkg_resources
+pkg_resources.require( 'MarkupSafe' )
+pkg_resources.require( 'Mako' )
+import mako
+
+
+# ============================================================================= exceptions
+class PluginFrameworkException( Exception ):
+ """Base exception for plugin frameworks.
+ """
+ pass
+class PluginFrameworkConfigException( PluginFrameworkException ):
+ """Exception for plugin framework configuration errors.
+ """
+ pass
+class PluginFrameworkStaticException( PluginFrameworkException ):
+ """Exception for plugin framework static directory set up errors.
+ """
+ pass
+class PluginFrameworkTemplateException( PluginFrameworkException ):
+ """Exception for plugin framework template directory
+ and template rendering errors.
+ """
+ pass
+
+
+# ============================================================================= base
+class PluginFramework( object ):
+ """
+ Plugins are files/directories living outside the Galaxy ``lib`` directory
+ that serve static files (css, js, images, etc.), use and serve mako templates,
+ and have some configuration to control the rendering.
+
+ A plugin framework sets up all the above components.
+ """
+ #: does the class need a config file(s) to be parsed?
+ has_config = True
+ #: does the class need static files served?
+ serves_static = True
+ #: does the class need template files served?
+ serves_templates = True
+ #TODO: allow plugin mako inheritance from existing ``/templates`` files
+ #uses_galaxy_templates = True
+ #TODO: possibly better as instance var (or a combo)
+ #: the directories in ``plugin_directory`` with basenames listed here will
+ #: be ignored for config, static, and templates
+ non_plugin_directories = []
+
+ # ------------------------------------------------------------------------- setup
+ @classmethod
+ def from_config( cls, config_plugin_directory, config ):
+ """
+ Set up the framework based on data from some config object by:
+ * constructing it's absolute plugin_directory filepath
+ * getting a template_cache
+ * and appending itself to the config object's ``plugin_frameworks`` list
+
+ .. note::
+ precondition: config obj should have attributes:
+ root, template_cache, and (list) plugin_frameworks
+ """
+ # currently called from (base) app.py - defined here to allow override if needed
+ if not config_plugin_directory:
+ return None
+ try:
+ full_plugin_filepath = os.path.join( config.root, config_plugin_directory )
+ template_cache = config.template_cache if cls.serves_static else None
+ plugin = cls( full_plugin_filepath, template_cache )
+
+ config.plugin_frameworks.append( plugin )
+ return plugin
+
+ except PluginFrameworkException, plugin_exc:
+ log.exception( "Error loading framework %s. Skipping...", cls.__class__.__name__ )
+ return None
+
+ def __str__( self ):
+ return '%s(%s)' %( self.__class__.__name__, self.plugin_directory )
+
+ def __init__( self, plugin_directory, template_cache_dir=None, debug=False ):
+ if not os.path.isdir( plugin_directory ):
+ raise PluginFrameworkException( 'Framework plugin directory not found: %s, %s'
+ %( self.__class__.__name__, plugin_directory ) )
+ # absolute (?) path
+ self.plugin_directory = plugin_directory
+ self.name = os.path.basename( self.plugin_directory )
+
+ if self.has_config:
+ self.load_configuration()
+ # set_up_static_urls will be called during the static middleware creation (if serves_static)
+ if self.serves_templates:
+ self.set_up_templates( template_cache_dir )
+
+ def get_plugin_directories( self ):
+ """
+ Return the plugin directory paths for this plugin.
+
+ Gets any directories within ``plugin_directory`` that are directories
+ themselves and whose ``basename`` is not in ``plugin_directory``.
+ """
+ # could instead explicitly list on/off in master config file
+ for plugin_path in glob.glob( os.path.join( self.plugin_directory, '*' ) ):
+ if not os.path.isdir( plugin_path ):
+ continue
+
+ if os.path.basename( plugin_path ) in self.non_plugin_directories:
+ continue
+
+ yield plugin_path
+
+ # ------------------------------------------------------------------------- config
+ def load_configuration( self ):
+ """
+ Override to load some framework/plugin specifc configuration.
+ """
+ # Abstract method
+ return True
+
+ # ------------------------------------------------------------------------- serving static files
+ def get_static_urls_and_paths( self ):
+ """
+ For each plugin, return a 2-tuple where the first element is a url path
+ to the plugin's static files and the second is a filesystem path to those
+ same files.
+
+ Meant to be passed to a Static url map.
+ """
+ url_and_paths = []
+ # called during the static middleware creation (buildapp.py, wrap_in_static)
+
+ # NOTE: this only searches for static dirs two levels deep (i.e. //static)
+ for plugin_path in self.get_plugin_directories():
+ # that path is a plugin, search for subdirs named static in THAT dir
+ plugin_static_path = os.path.join( plugin_path, 'static' )
+ if not os.path.isdir( plugin_static_path ):
+ continue
+
+ # build a url for that static subdir and create a Static urlmap entry for it
+ plugin_name = os.path.splitext( os.path.basename( plugin_path ) )[0]
+ plugin_url = self.name + '/' + plugin_name + '/static'
+ url_and_paths.append( ( plugin_url, plugin_static_path ) )
+
+ return url_and_paths
+
+ # ------------------------------------------------------------------------- templates
+ def set_up_templates( self, template_cache_dir ):
+ """
+ Add a ``template_lookup`` attribute to the framework that can be passed
+ to the mako renderer to find templates.
+ """
+ if not template_cache_dir:
+ raise PluginFrameworkTemplateException( 'Plugins that serve templates require a template_cache_dir' )
+ self.template_lookup = self._create_mako_template_lookup( template_cache_dir, self._get_template_paths() )
+ return self.template_lookup
+
+ def _get_template_paths( self ):
+ """
+ Get the paths that will be searched for templates.
+ """
+ return [ self.plugin_directory ]
+
+ def _create_mako_template_lookup( self, cache_dir, paths, collection_size=500, output_encoding='utf-8' ):
+ """
+ Create a ``TemplateLookup`` with defaults.
+ """
+ return mako.lookup.TemplateLookup(
+ directories = paths,
+ module_directory = cache_dir,
+ collection_size = collection_size,
+ output_encoding = output_encoding )
+
+ #TODO: do we want to remove trans and app from the plugin template context?
+ def fill_template( self, trans, template_filename, **kwargs ):
+ """
+ Pass control over to trans and render the ``template_filename``.
+ """
+ # defined here to be overridden
+ return trans.fill_template( template_filename, template_lookup=self.template_lookup, **kwargs )
+
+ def fill_template_with_plugin_imports( self, trans, template_filename, **kwargs ):
+ """
+ Returns a rendered plugin template but allows importing modules from inside
+ the plugin directory within the template.
+
+ ..example:: I.e. given this layout for a plugin:
+ bler/
+ template/
+ bler.mako
+ static/
+ conifg/
+ my_script.py
+ this version of `fill_template` allows `bler.mako` to call `import my_script`.
+ """
+ try:
+ plugin_base_path = os.path.split( os.path.dirname( template_filename ) )[0]
+ plugin_path = os.path.join( self.plugin_directory, plugin_base_path )
+ sys.path.append( plugin_path )
+ filled_template = self.fill_template( trans, template_filename, **kwargs )
+
+ finally:
+ sys.path.remove( plugin_path )
+
+ return filled_template
+
+ #TODO: could add plugin template helpers here
diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py
index 7eb98b13732..82b34131080 100644
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -993,10 +993,13 @@ class GalaxyWebTransaction( base.DefaultWebTransaction ):
searchList=[kwargs, self.template_context, dict(caller=self, t=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app)] )
return str( template )
- def fill_template_mako( self, filename, **kwargs ):
- template = self.webapp.mako_template_lookup.get_template( filename )
+ def fill_template_mako( self, filename, template_lookup=None, **kwargs ):
+ template_lookup = template_lookup or self.webapp.mako_template_lookup
+ template = template_lookup.get_template( filename )
template.output_encoding = 'utf-8'
- data = dict( caller=self, t=self, trans=self, h=helpers, util=util, request=self.request, response=self.response, app=self.app )
+
+ data = dict( caller=self, t=self, trans=self, h=helpers, util=util,
+ request=self.request, response=self.response, app=self.app )
data.update( self.template_context )
data.update( kwargs )
return template.render( **data )
diff --git a/lib/galaxy/web/framework/middleware/static.py b/lib/galaxy/web/framework/middleware/static.py
index 5b581532bec..cbbc17c9816 100644
--- a/lib/galaxy/web/framework/middleware/static.py
+++ b/lib/galaxy/web/framework/middleware/static.py
@@ -12,9 +12,11 @@ from paste.httpheaders import ETAG
from paste.urlparser import StaticURLParser
class CacheableStaticURLParser( StaticURLParser ):
+
def __init__( self, directory, cache_seconds=None ):
StaticURLParser.__init__( self, directory )
self.cache_seconds = cache_seconds
+
def __call__( self, environ, start_response ):
path_info = environ.get('PATH_INFO', '')
if not path_info:
diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py
index 675b2c23fd3..9ec41a2db0e 100644
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -2,22 +2,26 @@
Provides factory methods to assemble the Galaxy web application
"""
-import logging, atexit
-import os, os.path
-import sys, warnings
-
-from galaxy.util import asbool
+import sys
+import os
+import os.path
+import atexit
+import warnings
+import glob
from paste import httpexceptions
import pkg_resources
-log = logging.getLogger( __name__ )
-
-from galaxy import util
import galaxy.model
import galaxy.model.mapping
import galaxy.datatypes.registry
import galaxy.web.framework
+from galaxy import util
+from galaxy.util import asbool
+
+import logging
+log = logging.getLogger( __name__ )
+
class GalaxyWebApplication( galaxy.web.framework.WebApplication ):
pass
@@ -181,7 +185,7 @@ def app_factory( global_conf, **kwargs ):
if kwargs.get( 'middleware', True ):
webapp = wrap_in_middleware( webapp, global_conf, **kwargs )
if asbool( kwargs.get( 'static_enabled', True ) ):
- webapp = wrap_in_static( webapp, global_conf, **kwargs )
+ webapp = wrap_in_static( webapp, global_conf, plugin_frameworks=app.config.plugin_frameworks, **kwargs )
if asbool(kwargs.get('pack_scripts', False)):
pack_scripts()
# Close any pooled database connections before forking
@@ -323,7 +327,7 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
log.debug( "Enabling 'Request ID' middleware" )
return app
-def wrap_in_static( app, global_conf, **local_conf ):
+def wrap_in_static( app, global_conf, plugin_frameworks=None, **local_conf ):
from paste.urlmap import URLMap
from galaxy.web.framework.middleware.static import CacheableStaticURLParser as Static
urlmap = URLMap()
@@ -343,6 +347,16 @@ def wrap_in_static( app, global_conf, **local_conf ):
urlmap["/static/style"] = Static( conf.get( "static_style_dir" ), cache_time )
urlmap["/favicon.ico"] = Static( conf.get( "static_favicon_dir" ), cache_time )
urlmap["/robots.txt"] = Static( conf.get( "static_robots_txt", 'static/robots.txt'), cache_time )
+
+ # wrap any static dirs for plugins
+ plugin_frameworks = plugin_frameworks or []
+ for static_serving_framework in ( framework for framework in plugin_frameworks if framework.serves_static ):
+ # invert control to each plugin for finding their own static dirs
+ for plugin_url, plugin_static_path in static_serving_framework.get_static_urls_and_paths():
+ plugin_url = '/plugins/' + plugin_url
+ urlmap[( plugin_url )] = Static( plugin_static_path, cache_time )
+ log.debug( 'added url, path to static middleware: %s, %s', plugin_url, plugin_static_path )
+
# URL mapper becomes the root webapp
return urlmap
diff --git a/lib/galaxy/webapps/galaxy/controllers/visualization.py b/lib/galaxy/webapps/galaxy/controllers/visualization.py
index 0e963a693dd..57fe8a60a9c 100644
--- a/lib/galaxy/webapps/galaxy/controllers/visualization.py
+++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py
@@ -708,7 +708,7 @@ class VisualizationController( BaseUIController, SharableMixin, UsesAnnotations,
# validate name vs. registry
registry = trans.app.visualizations_registry
if not registry:
- raise HTTPNotFound( 'No visualization registry (possibly disabled in universe_wsgi.ini)')
+ raise HTTPNotFound( 'No visualization registry (possibly disabled in universe_wsgi.ini)' )
if visualization_name not in registry.listings:
raise HTTPNotFound( 'Unknown or invalid visualization: ' + visualization_name )
# or redirect to list?
@@ -722,16 +722,15 @@ class VisualizationController( BaseUIController, SharableMixin, UsesAnnotations,
resources = registry.query_dict_to_resources( trans, self, visualization_name, kwargs )
# look up template and render
- template_root = registry_listing.get( 'template_root', registry.TEMPLATE_ROOT )
- template = registry_listing[ 'template' ]
- template_path = os.path.join( template_root, template )
+ template_path = registry_listing[ 'template' ]
+ returned = registry.fill_template( trans, template_path,
+ visualization_name=visualization_name, query_args=kwargs,
+ embedded=embedded, shared_vars={}, **resources )
#NOTE: passing *unparsed* kwargs as query_args
#NOTE: shared_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
#TODO: embedded
- returned = trans.fill_template( template_path, visualization_name=visualization_name,
- embedded=embedded, query_args=kwargs, shared_vars={}, **resources )
except Exception, exception:
log.exception( 'error rendering visualization (%s): %s', visualization_name, str( exception ) )
diff --git a/static/scripts/mvc/dataset/hda-edit.js b/static/scripts/mvc/dataset/hda-edit.js
index 296cb8127df..f78894e0a6e 100644
--- a/static/scripts/mvc/dataset/hda-edit.js
+++ b/static/scripts/mvc/dataset/hda-edit.js
@@ -616,7 +616,7 @@ function create_scatterplot_action_fn( url, params ){
title : "Scatterplot",
type : "url",
content : url + '/scatterplot?' + $.param(params),
- center : true
+ location : 'center'
});
//TODO: this needs to go away
@@ -699,4 +699,4 @@ function create_trackster_action_fn(vis_url, dataset_params, dbkey) {
//==============================================================================
//return {
// HDAView : HDAView,
-//};});
\ No newline at end of file
+//};});
diff --git a/universe_wsgi.ini.sample b/universe_wsgi.ini.sample
index 48aa4adfb55..4c235982d71 100644
--- a/universe_wsgi.ini.sample
+++ b/universe_wsgi.ini.sample
@@ -174,10 +174,8 @@ paste.app_factory = galaxy.web.buildapp:app_factory
# Galaxy.
#datatypes_config_file = datatypes_conf.xml
-# Visualizations config directory, where to look for individual visualization
-# xml configuration files. Those files define how visualizations apply to
-# particular data and how to pass them the necessary parameters
-#visualizations_config_directory = config/visualizations
+# Visualizations config directory: where to look for individual visualization plugins.
+#visualizations_plugins_directory = config/plugins/visualizations
# Each job is given a unique empty directory as its current working directory.
# This option defines in what parent directory those directories will be