Changed how TemplatePlugin works

get_template_vars get split into get_template_vars (for injection additional template variables only) and get_template_configs (for configuring injection of additional template). It's now possible to inject more than one of a given template type from within plugins, and template injection is more intelligent in that if a plugin's templates don't need special configuration and stick to default naming and behaviour, just declaring them inside the templates directory of the plugin will be enough to fire up everything including links to switch to tabs, sidebars etc (the plugin's name being used in such cases for link text).
This commit is contained in:
Gina Häußge 2015-01-16 11:54:34 +01:00
parent 19fe770606
commit 5180fc70d2
7 changed files with 354 additions and 195 deletions

View file

@ -242,6 +242,8 @@ class PluginManager(object):
kwargs = dict(additional_injects)
kwargs.update(dict(
identifier=name,
plugin_name=plugin.name,
plugin_version=plugin.version,
basefolder=os.path.realpath(plugin.location),
logger=logging.getLogger("octoprint.plugins." + name),
))

View file

@ -113,18 +113,53 @@ class TemplatePlugin(Plugin):
Navbar
The right part of the navigation bar located at the top of the UI can be enriched with additional links. Note that
with the current implementation, plugins will always be located *to the left* of the existing links.
The included template must be called ``<pluginname>_navbar.jinja2`` (e.g. ``myplugin_navbar.jinja2``) unless
overridden by the configuration supplied through ``get_template_config``.
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
wrapper structure will have all additional classes and styles applied as specified via the configuration supplied
through ``get_template_config``.
Sidebar
The left side bar containing Connection, State and Files sections can be enriched with additional sections. Note
that with the current implementations, plugins will always be located *beneath* the existing sections.
The included template must be called ``<pluginname>_sidebar.jinja2`` (e.g. ``myplugin_sidebar.jinja2``) unless
overridden by the configuration supplied through ``get_template_config``.
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
wrapper divs for both the whole box as well as the content pane will have all additional classes and styles applied
as specified via the configuration supplied through ``get_template_config``.
Tabs
The available tabs of the main part of the interface may be extended with additional tabs originating from within
plugins. Note that with the current implementation, plugins will always be located *to the right* of the existing
tabs.
The included template must be called ``<pluginname>_tab.jinja2`` (e.g. ``myplugin_tab.jinja2``) unless
overridden by the configuration supplied through ``get_template_config``.
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
wrapper div and the link in the navigation will have the additional classes and styles applied as specified via the
configuration supplied through ``get_template_config``.
Settings
Plugins may inject a dialog into the existing settings view. Note that with the current implementations, plugins
will always be listed beneath the "Plugins" header in the settings link list, ordered alphabetically after
their displayed name.
The included template must be called ``<pluginname>_settings.jinja2`` (e.g. ``myplugin_settings.jinja2``) unless
overridden by the configuration supplied through ``get_template_config``.
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
wrapper div and the link in the navigation will have the additional classes and styles applied as defined via the
supplied configuration supplied through ``get_template_config``.
Generic
Plugins may also inject arbitrary templates into the page of the web interface itself, e.g. in order to
add overlays or dialogs to be called from within the plugin's javascript code.
.. figure:: ../images/template-plugin-types-main.png
:align: center
:alt: Template injection types in the main part of the interface
@ -137,121 +172,182 @@ class TemplatePlugin(Plugin):
Template injection types in the settings
Which components a plugin supplies is controlled by a number of special template variables returned by the overridden
``get_template_vars`` method. The following special keys are supported:
_settings
Configures the settings component to inject. The value must be a dictionary, supported values are the following:
name
The name under which to include the settings pane, will be visible in the navigation tree on the left of the
settings dialog
custom_bindings
A boolean value indicating whether the default settings view model should be bound to the settings pane (``false``, default)
or if a custom binding will be used by the plugin (``true``)
data_bind
Additional knockout data bindings to apply to the template container, can be used to add further behaviour to
the container based on internal state if necessary. Note that if you include this and set ``custom_bindings``
to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise
it won't work.
The included settings dialog template file must be called ``<plugin name>_settings.jinja2`` (e.g. ``myplugin_settings.jinja2``).
The template will be already included in the necessary wrapper divs, you just need to supply the pure content.
_tab
Configures the tab component to inject. The value must be a dictionary, supported values are the following:
name
The name under which to include the tab, will be visible in the list of tabs on the top of the main view
data_bind
Additional knockout data bindings to apply to the template container, can be used to add further behaviour to
the container based on internal state if necessary. Note that if you include this and set ``custom_bindings``
to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise
it won't work.
The template included into the tab pane section must be called ``<plugin name>_tab.jinja2`` (e.g. ``myplugin_tab.jinja2``).
The template will be already included in the necessary wrapper divs, you just need to supply the pure content.
_sidebar
Configures the sidebar component to inject. The value must be a dictionary, supported values are the following:
name
The name of the sidebar entry
icon
Icon to use for the sidebar header, should be the name of a Font Awesome icon without the leading ``icon-`` part
header_addon
Additional template to include in the head section of the sidebar item. For an example of this, see the additional
options included in the "Files" section
data_bind
Additional knockout data bindings to apply to the template container, can be used to add further behaviour to
the container based on internal state if necessary. Note that if you include this and set ``custom_bindings``
to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise
it won't work.
The template included into the sidebar section must be called ``<plugin name>_sidebar.jinja2`` (e.g. ``myplugin_sidebar.jinja2``).
The template will be already included in the necessary wrapper divs, you just need to supply the pure content.
_navbar
Configures the navbar component to inject. The value must be an empty dictionary.
The template included into the sidebar section must be called ``<plugin name>_navbar.jinja2``
The following is an example for a simple plugin which injects all four types of templates into the interfaces:
``helloworld/__init__.py``
.. code-block:: python
import octoprint.plugin
class HelloWorldPlugin(octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin):
def get_template_vars(self):
return dict(
_settings=dict(name="Hello World"),
_tab=dict(name="Hello World", custom_bindings=True, data_bind="allowBindings: true, visible: self.loginState.isUser()"),
_sidebar=dict(name="Hello World", icon="gear"),
_navbar=dict()
)
def get_assets(self):
return dict(
js=["js/helloworld.js"]
)
__plugin_name__ = "Hello World"
__plugin_version__ = "1.0"
__plugin_implementations__ = [HelloWorldPlugin()]
``helloworld/templates/helloworld_settings.jinja2``
.. code-block:: html+jinja
<h1>Hello World!</h1>
This is a custom settings pane.
``helloworld/templates/helloworld_tab.jinja2``
.. code-block:: html+jinja
<h1>Hello World!</h1>
This is a custom tab.
``helloworld/templates/helloworld_sidebar.jinja2``
.. code-block:: html+jinja
Hello World! This is a custom side bar.
``helloworld/templates/helloworld_navbar.jinja2``
.. code-block:: html+jinja
<li>
<a class="pull-right" href="http://www.example.com"><i class="icon-gear"></i> {{ _('Hello World!') }}</a>
</li>
You can find an example for a simple plugin which injects navbar, sidebar, tab and settings content into the interface in
`the "helloworld" plugin in OctoPrint's collection of plugin examples <https://github.com/OctoPrint/Plugin-Examples/tree/master/helloworld>`_.
"""
def get_template_configs(self):
"""
Allows configuration of injected navbar, sidebar, tab and settings templates. Should be a list containing one
configuration object per template to inject. Each configuration object is represented by a dictionary with a mandatory key
``type`` encoding the template type the configuration is targeting. Possible values here are ``navbar``, ``sidebar``,
``tab``, ``settings`` and ``generic``.
Further keys to be included in the dictionary depend on the type:
``navbar`` type
Configures a navbar component to inject. The following keys are supported:
template
Name of the template to inject, defaults to ``<pluginname>_navbar.jinja2``.
suffix
Suffix to attach to the element ID of the injected template, will be ``_<index>`` if not provided and not
the first template of the type, with ``index`` counting from 1 and increasing for each template of the same
type.
custom_bindings
A boolean value indicating whether the default view model should be bound to the navbar entry (``false``)
or if a custom binding will be used by the plugin (``true``, default).
data_bind
Additional knockout data bindings to apply to the navbar entry, can be used to add further behaviour to
the container based on internal state if necessary. Note that if you include this and set ``custom_bindings``
to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise
it won't work.
classes
Additional classes to apply to the navbar entry, as a list of individual classes
(e.g. ``classes=["myclass", "myotherclass"]``) which will be joined into the correct format by the template engine.
styles
Additional CSS styles to apply to the navbar entry, as a list of individual declarations
(e.g. ``styles=["color: red", "display: block"]``) which will be joined into the correct format by the template
engine.
``sidebar`` type
Configures a sidebar component to inject. The following keys are supported:
name
The name of the sidebar entry, if not set the name of the plugin will be used.
icon
Icon to use for the sidebar header, should be the name of a Font Awesome icon without the leading ``icon-`` part.
template
Name of the template to inject, defaults to ``<pluginname>_sidebar.jinja2``.
template_header
Additional template to include in the head section of the sidebar item. For an example of this, see the additional
options included in the "Files" section.
suffix
Suffix to attach to the element ID of the injected template, will be ``_<index>`` if not provided and not
the first template of the type, with ``index`` counting from 1 and increasing for each template of the same
type.
custom_bindings
A boolean value indicating whether the default view model should be bound to the sidebar container (``false``)
or if a custom binding will be used by the plugin (``true``, default).
data_bind
Additional knockout data bindings to apply to the template container, can be used to add further behaviour to
the container based on internal state if necessary. Note that if you include this and set ``custom_bindings``
to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise
it won't work.
classes
Additional classes to apply to both the wrapper around the sidebar box as well as the content pane itself, as a
list of individual classes (e.g. ``classes=["myclass", "myotherclass"]``) which will be joined into the correct
format by the template engine.
classes_wrapper
Like ``classes`` but only applied to the whole wrapper around the sidebar box.
classes_content
Like ``classes`` but only applied to the content pane itself.
styles
Additional CSS styles to apply to both the wrapper around the sidebar box as well as the content pane itself,
as a list of individual declarations (e.g. ``styles=["color: red", "display: block"]``) which will be joined
into the correct format by the template engine.
styles_wrapper
Like ``styles`` but only applied to the whole wrapper around the sidebar box.
styles_content
Like ``styles`` but only applied to the content pane itself
``tab`` type
Configures a tab component to inject. The value must be a dictionary, supported values are the following:
name
The name under which to include the tab, if not set the name of the plugin will be used.
template
Name of the template to inject, defaults to ``<pluginname>_tab.jinja2``.
suffix
Suffix to attach to the element ID of the injected template, will be ``_<index>`` if not provided and not
the first template of the type, with ``index`` counting from 1 and increasing for each template of the same
type.
custom_bindings
A boolean value indicating whether the default view model should be bound to the tab pane and link
in the navigation (``false``) or if a custom binding will be used by the plugin (``true``, default).
data_bind
Additional knockout data bindings to apply to the template container, can be used to add further behaviour to
the container based on internal state if necessary. Note that if you include this and set ``custom_bindings``
to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise
it won't work.
classes
Additional classes to apply to both the wrapper around the sidebar box as well as the content pane itself, as a
list of individual classes (e.g. ``classes=["myclass", "myotherclass"]``) which will be joined into the correct
format by the template engine.
classes_link
Like ``classes`` but only applied to the link in the navigation.
classes_content
Like ``classes`` but only applied to the content pane itself.
styles
Additional CSS styles to apply to both the wrapper around the sidebar box as well as the content pane itself,
as a list of individual declarations (e.g. ``styles=["color: red", "display: block"]``) which will be joined
into the correct format by the template engine.
styles_link
Like ``styles`` but only applied to the link in the navigation.
styles_content
Like ``styles`` but only applied to the content pane itself
``settings`` type
Configures a settings component to inject. The value must be a dictionary, supported values are the following:
name
The name under which to include the settings pane, if not set the name of the plugin will be used.
template
Name of the template to inject, defaults to ``<pluginname>_settings.jinja2``.
suffix
Suffix to attach to the element ID of the injected template, will be ``_<index>`` if not provided and not
the first template of the type, with ``index`` counting from 1 and increasing for each template of the same
type.
custom_bindings
A boolean value indicating whether the default settings view model should be bound to the settings pane and link
in the navigation (``false``) or if a custom binding will be used by the plugin (``true``, default).
data_bind
Additional knockout data bindings to apply to the template container, can be used to add further behaviour to
the container based on internal state if necessary. Note that if you include this and set ``custom_bindings``
to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise
it won't work.
classes
Additional classes to apply to both the wrapper around the navigation link as well as the content pane itself, as a
list of individual classes (e.g. ``classes=["myclass", "myotherclass"]``) which will be joined into the correct
format by the template engine.
classes_link
Like ``classes`` but only applied to the link in the navigation.
classes_content
Like ``classes`` but only applied to the content pane itself.
styles
Additional CSS styles to apply to both the wrapper around the navigation link as well as the content pane itself,
as a list of individual declarations (e.g. ``styles=["color: red", "display: block"]``) which will be joined
into the correct format by the template engine.
styles_link
Like ``styles`` but only applied to the link in the navigation.
styles_content
Like ``styles`` but only applied to the content pane itself
``generic`` type
Configures a generic template to inject. The following keys are supported:
template
Name of the template to inject, defaults to ``<pluginname>_settings.jinja2``.
.. note::
As already outlined above, each template type has a default template name (i.e. the default navbar template
of a plugin is called ``<pluginname>_navbar.jinja2``), which may be overridden using the template configuration.
If a plugin needs to include two navbar templates, it needs to provide two entries here, at least one with ``template``
overridden. If only one entry for a given template type is present with an overridden ``template`` parameter,
OctoPrint will assume that the default template does not exist and hence only include the defined template,
skipping the default one.
:return: a list containing the configuration options for the plugin's injected templates
"""
return []
def get_template_vars(self):
"""
Defines additional template variables to include into the template renderer.
:return: a dictionary containing any additional template variables to include in the renderer
"""
return dict()
def get_template_folder(self):

View file

@ -169,13 +169,6 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
self._cura_logger.setLevel(logging.CRITICAL)
s.setBoolean(["debug_logging"], new_debug_logging)
##~~ TemplatePlugin API
def get_template_vars(self):
return dict(
_settings=dict(name="Cura", custom_bindings=True)
)
##~~ SlicerPlugin API
def is_slicer_configured(self):

View file

@ -13,6 +13,7 @@ from flask.ext.principal import Principal, Permission, RoleNeed, identity_loaded
from flask.ext.babel import Babel, gettext, ngettext
from babel import Locale
from watchdog.observers import Observer
from collections import defaultdict
import os
import logging
@ -99,6 +100,8 @@ def get_locale():
@app.route("/")
def index():
#~~ extract data from asset plugins
asset_plugins = pluginManager.get_implementations(octoprint.plugin.AssetPlugin)
@ -110,91 +113,85 @@ def index():
template_plugins = pluginManager.get_implementations(octoprint.plugin.TemplatePlugin)
# rules for transforming template configs to template entries
rules = dict(
navbar=dict(div=lambda x: "navbar_plugin_" + x, template=lambda x: x + "_navbar.jinja2", to_entry=lambda data: data),
sidebar=dict(div=lambda x: "sidebar_plugin_" + x, template=lambda x: x + "_sidebar.jinja2", to_entry=lambda data: (data["name"], data)),
tab=dict(div=lambda x: "tab_plugin_" + x, template=lambda x: x + "_tab.jinja2", to_entry=lambda data: (data["name"], data)),
settings=dict(div=lambda x: "settings_plugin_" + x, template=lambda x: x + "_settings.jinja2", to_entry=lambda data: (data["name"], data)),
generic=dict(template=lambda x: x + ".jinja2", to_entry=lambda data: data)
)
plugin_vars = dict()
plugin_includes_navbar = []
plugin_includes_sidebar = []
plugin_includes_tabs = []
plugin_includes_settings = []
plugin_includes_generic = []
plugin_names = template_plugins.keys()
for name, implementation in template_plugins.items():
vars = implementation.get_template_vars()
if not isinstance(vars, dict):
continue
if "_navbar" in vars and isinstance(vars["_navbar"], dict):
data = dict(vars["_navbar"])
data["_div"] = "navbar_plugin_" + name
data["_template"] = name + "_navbar.jinja2"
plugin_includes_navbar.append(data)
del vars["_navbar"]
if "_sidebar" in vars and isinstance(vars["_sidebar"], dict) and "name" in vars["_sidebar"]:
data = dict(vars["_sidebar"])
data["_div"] = "sidebar_plugin_" + name
data["_template"] = name + "_sidebar.jinja2"
plugin_includes_sidebar.append((data["name"], data))
del vars["_sidebar"]
if "_tab" in vars and isinstance(vars["_tab"], dict) and "name" in vars["_tab"]:
data = dict(vars["_tab"])
data["_div"] = "tab_plugin_" + name
data["_template"] = name + "_tab.jinja2"
plugin_includes_tabs.append((data["name"], data))
del vars["_tab"]
if "_settings" in vars and isinstance(vars["_settings"], dict) and "name" in vars["_settings"]:
data = dict(vars["_settings"])
data["_div"] = "settings_plugin_" + name
data["_template"] = name + "_settings.jinja2"
plugin_includes_settings.append((data["name"], data))
del vars["_settings"]
vars = dict()
for var_name, var_value in vars.items():
plugin_vars["plugin_" + name + "_" + var_name] = var_value
configs = implementation.get_template_configs()
if not isinstance(configs, (list, tuple)):
configs = []
includes = _process_template_configs(name, implementation, configs, rules)
plugin_includes_navbar += includes["navbar"]
plugin_includes_sidebar += includes["sidebar"]
plugin_includes_tabs += includes["tab"]
plugin_includes_settings += includes["settings"]
plugin_includes_generic += includes["generic"]
#~~ navbar
navbar_entries = plugin_includes_navbar + [
dict(_template="navbar/settings.jinja2", _div="navbar_settings", styles=["display: none"], data_bind="visible: loginState.isAdmin"),
dict(_template="navbar/systemmenu.jinja2", _div="navbar_systemmenu", styles=["display: none"], classes=["dropdown"], data_bind="visible: loginState.isAdmin"),
dict(_template="navbar/login.jinja2", _div="navbar_login", classes=["dropdown"])
dict(template="navbar/settings.jinja2", _div="navbar_settings", styles=["display: none"], data_bind="visible: loginState.isAdmin", custom_bindings=False),
dict(template="navbar/systemmenu.jinja2", _div="navbar_systemmenu", styles=["display: none"], classes=["dropdown"], data_bind="visible: loginState.isAdmin", custom_bindings=False),
dict(template="navbar/login.jinja2", _div="navbar_login", classes=["dropdown"], custom_bindings=False)
]
#~~ sidebar
sidebar_entries = [
(gettext("Connection"), dict(_template="sidebar/connection.jinja2", _div="connection", icon="signal", styles_wrapper=["display: none"], data_bind="visible: loginState.isAdmin")),
(gettext("State"), dict(_template="sidebar/state.jinja2", _div="state", icon="info-sign")),
(gettext("Files"), dict(_template="sidebar/files.jinja2", _div="files", icon="list", classes_content=["overflow_visible"], header_addon="sidebar/files_header.jinja2"))
(gettext("Connection"), dict(template="sidebar/connection.jinja2", _div="connection", icon="signal", styles_wrapper=["display: none"], data_bind="visible: loginState.isAdmin")),
(gettext("State"), dict(template="sidebar/state.jinja2", _div="state", icon="info-sign")),
(gettext("Files"), dict(template="sidebar/files.jinja2", _div="files", icon="list", classes_content=["overflow_visible"], header_addon="sidebar/files_header.jinja2"))
] + plugin_includes_sidebar
#~~ tabs
tab_entries = [
(gettext("Temperature"), dict(_template="tabs/temperature.jinja2", _div="temp")),
(gettext("Control"), dict(_template="tabs/control.jinja2", _div="control")),
(gettext("GCode Viewer"), dict(_template="tabs/gcodeviewer.jinja2", _div="gcode")),
(gettext("Terminal"), dict(_template="tabs/terminal.jinja2", _div="term")),
(gettext("Timelapse"), dict(_template="tabs/timelapse.jinja2", _div="timelapse"))
(gettext("Temperature"), dict(template="tabs/temperature.jinja2", _div="temp")),
(gettext("Control"), dict(template="tabs/control.jinja2", _div="control")),
(gettext("GCode Viewer"), dict(template="tabs/gcodeviewer.jinja2", _div="gcode")),
(gettext("Terminal"), dict(template="tabs/terminal.jinja2", _div="term")),
(gettext("Timelapse"), dict(template="tabs/timelapse.jinja2", _div="timelapse"))
] + plugin_includes_tabs
#~~ settings dialog
settings_entries = [
(gettext("Printer"), None),
(gettext("Serial Connection"), dict(_template="dialogs/settings/serialconnection.jinja2", _div="settings_serialConnection")),
(gettext("Printer Profiles"), dict(_template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles")),
(gettext("Temperatures"), dict(_template="dialogs/settings/temperatures.jinja2", _div="settings_temperature")),
(gettext("Terminal Filters"), dict(_template="dialogs/settings/terminalfilters.jinja2", _div="settings_terminalFilters")),
(gettext("Serial Connection"), dict(template="dialogs/settings/serialconnection.jinja2", _div="settings_serialConnection", custom_bindings=False)),
(gettext("Printer Profiles"), dict(template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles", custom_bindings=False)),
(gettext("Temperatures"), dict(template="dialogs/settings/temperatures.jinja2", _div="settings_temperature", custom_bindings=False)),
(gettext("Terminal Filters"), dict(template="dialogs/settings/terminalfilters.jinja2", _div="settings_terminalFilters", custom_bindings=False)),
(gettext("Features"), None),
(gettext("Features"), dict(_template="dialogs/settings/features.jinja2", _div="settings_features")),
(gettext("Webcam"), dict(_template="dialogs/settings/webcam.jinja2", _div="settings_webcam")),
(gettext("Access Control"), dict(_template="dialogs/settings/accesscontrol.jinja2", _div="settings_users")),
(gettext("API"), dict(_template="dialogs/settings/api.jinja2", _div="settings_api")),
(gettext("Features"), dict(template="dialogs/settings/features.jinja2", _div="settings_features", custom_bindings=False)),
(gettext("Webcam"), dict(template="dialogs/settings/webcam.jinja2", _div="settings_webcam", custom_bindings=False)),
(gettext("Access Control"), dict(template="dialogs/settings/accesscontrol.jinja2", _div="settings_users", custom_bindings=False)),
(gettext("API"), dict(template="dialogs/settings/api.jinja2", _div="settings_api", custom_bindings=False)),
(gettext("OctoPrint"), None),
(gettext("Folders"), dict(_template="dialogs/settings/folders.jinja2", _div="settings_folders")),
(gettext("Appearance"), dict(_template="dialogs/settings/appearance.jinja2", _div="settings_appearance")),
(gettext("Logs"), dict(_template="dialogs/settings/logs.jinja2", _div="settings_logs", data_bind="allowBindings: false"))
(gettext("Folders"), dict(template="dialogs/settings/folders.jinja2", _div="settings_folders", custom_bindings=False)),
(gettext("Appearance"), dict(template="dialogs/settings/appearance.jinja2", _div="settings_appearance", custom_bindings=False)),
(gettext("Logs"), dict(template="dialogs/settings/logs.jinja2", _div="settings_logs"))
]
if len(plugin_includes_settings):
settings_entries.append((gettext("Plugins"), None))
@ -222,6 +219,7 @@ def index():
sidebarEntries=sidebar_entries,
tabEntries=tab_entries,
settingsEntries=settings_entries,
genericEntries=plugin_includes_generic,
pluginNames=plugin_names,
assetPlugins=asset_plugin_urls,
)
@ -235,6 +233,76 @@ def index():
)
def _process_template_configs(name, implementation, configs, rules):
from jinja2.exceptions import TemplateNotFound
counters = dict(
navbar=1,
sidebar=1,
tab=1,
settings=1
)
includes = defaultdict(list)
for config in configs:
if not isinstance(config, dict):
continue
if not "type" in config:
continue
template_type = config["type"]
del config["type"]
if not template_type in rules:
continue
rule = rules[template_type]
data = _process_template_config(name, implementation, rule, config=config, counter=counters[template_type])
if data is None:
continue
includes[template_type].append(rule["to_entry"](data))
counters[template_type] += 1
for template_type in rules:
if len(includes[template_type]) == 0:
# if no template of that type was added by the config, we'll try to use the default template name
rule = rules[template_type]
data = _process_template_config(name, implementation, rule)
if data is not None:
try:
app.jinja_env.get_or_select_template(data["template"])
except TemplateNotFound:
pass
else:
includes[template_type].append(rule["to_entry"](data))
return includes
def _process_template_config(name, implementation, rule, config=None, counter=1):
if "mandatory" in rule:
for mandatory in rule["mandatory"]:
if not mandatory in config:
return None
if config is None:
config = dict()
data = dict(config)
if "div" in rule:
data["_div"] = rule["div"](name)
if "suffix" in data:
data["_div"] += "_" + data["suffix"]
del data["suffix"]
elif counter > 1:
data["_div"] += "_%d" % counter
if not "template" in data:
data["template"] = rule["template"](name)
if not "name" in data:
data["name"] = implementation._plugin_name
return data
@app.route("/robots.txt")
def robotsTxt():
return send_from_directory(app.static_folder, "robots.txt")

File diff suppressed because one or more lines are too long

View file

@ -11,15 +11,15 @@
{% if data is none %}
<li class="nav-header">{{ entry }}</li>
{% else %}
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
<li id="{{ data._div }}_link"
{% if "data_bind" in data %} data-bind="{{ data.data_bind }}" {% elif "custom_bindings" in data and data["custom_bindings"] %} data-bind="allowBindings: true" {% endif %}
{% if "data_bind" in data %} data-bind="{{ data.data_bind }}" {% elif "custom_bindings" not in data or data["custom_bindings"] %} data-bind="allowBindings: true" {% endif %}
class="{% if not active_set %}active{% set active_set = true %}{% endif %} {% if "classes_link" in data %}{{ data.classes_link|join(' ') }}{% elif "classes" in data %}{{ data.classes|join(' ') }}{% endif %}"
{% if "styles_link" in data %} style="{{ data.styles_link|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %}
>
<a href="#{{ data._div }}" data-toggle="tab">{{ entry }}</a>
</li>
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% endif %}
{% endfor %}
</ul>
@ -28,15 +28,15 @@
{% set active_set = false %}
{% for entry, data in settingsEntries %}
{% if data is not none %}
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
<div id="{{ data._div }}"
{% if "data_bind" in data %} data-bind="{{ data.data_bind }}" {% elif "custom_bindings" in data and data["custom_bindings"] %} data-bind="allowBindings: true" {% endif %}
{% if "data_bind" in data %} data-bind="{{ data.data_bind }}" {% elif "custom_bindings" not in data or data["custom_bindings"] %} data-bind="allowBindings: true" {% endif %}
class="tab-pane {% if not active_set %}active{% set active_set = true %}{% endif %} {% if classes_content in data %}{{ data.classes_content|join(' ') }}{% elif classes in data %}{{ data.classes|join(' ') }}{% endif %}"
{% if "styles_content" in data %} style="{{ data.styles_content|join(', ') }}" {% elif styles in data %} style="{{ data.styles|join(', ') }}" {% endif %}
>
{% include data._template ignore missing %}
{% include data.template ignore missing %}
</div>
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% endif %}
{% endfor %}
</div>

View file

@ -19,15 +19,15 @@
<!-- Navbar -->
<ul class="nav pull-right">
{% for data in navbarEntries %}
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
<li id="{{ data._div }}"
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% else %}allowBindings: true{% endif %}"
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" not in data or data["custom_bindings"] %}allowBindings: true{% endif %}"
{% if "classes" in data %}class="{{ data.classes|join(' ') }}"{% endif %}
{% if "styles" in data %}style="{{ data.styles|join(', ') }}"{% endif %}
>
{% include data._template ignore missing %}
{% include data.template ignore missing %}
</li>
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% endfor %}
</ul>
</div>
@ -39,10 +39,10 @@
<!-- Sidebar -->
<div class="accordion span4">
{% for entry, data in sidebarEntries %}
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
<div id="{{ data._div }}_wrapper"
class="accordion-group {% if "classes_wrapper" in data %}{{ data.classes_wrapper|join(' ') }}{% elif "classes" in data %}{{ data.classes|join(' ') }}{% endif %}"
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" in data and data["custom_bindings"] %}allowBindings: true{% endif %}"
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" not in data or data["custom_bindings"] %}allowBindings: true{% endif %}"
{% if "styles_wrapper" in data %} style="{{ data.styles_wrapper|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %}
>
<div class="accordion-heading">
@ -58,11 +58,11 @@
{% if "styles_content" in data %} style="{{ data.styles_content|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}"{% endif %}
>
<div class="accordion-inner">
{% include data._template ignore missing %}
{% include data.template ignore missing %}
</div>
</div>
</div>
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% endfor %}
</div>
@ -70,29 +70,29 @@
<div class="span8 tabbable">
<ul class="nav nav-tabs" id="tabs">
{% for entry, data in tabEntries %}
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
<li id="{{ data._div }}_link"
class="{% if loop.first %}active{% endif %} {% if "classes_link" in data %}{{ data.classes_link|join(' ') }}{% elif "classes" in data %}{{ data.classes|join(' ') }}{% endif %}"
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" in data and data["custom_bindings"] %}allowBindings: true{% endif %}"
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" not in data or data["custom_bindings"] %}allowBindings: true{% endif %}"
{% if "styles_link" in data %} style="{{ data.styles_link|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %}
>
<a href="#{{ data._div }}" data-toggle="tab">{{ entry }}</a>
</li>
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% endfor %}
</ul>
<div class="tab-content">
{% for entry, data in tabEntries %}
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
<div id="{{ data._div }}"
class="tab-pane{% if loop.first %} active{% endif %}{% if "additional_classes" in data %} {{ data.additional_classes|join(' ') }}{% endif %}"
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" in data and data["custom_bindings"] %}allowBindings: true{% endif %}"
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" not in data or data["custom_bindings"] %}allowBindings: true{% endif %}"
{% if "styles_content" in data %} style="{{ data.styles_content|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %}
>
{% include data._template ignore missing %}
{% include data.template ignore missing %}
</div>
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% endfor %}
</div>
</div>
@ -123,8 +123,8 @@
<!-- End of overlays -->
<!-- Generic plugin template files -->
{% for plugin_name in pluginNames %}
{% include plugin_name + ".jinja2" ignore missing %}
{% for data in genericEntries %}
{% include data.template ignore missing %}
{% endfor %}
<!-- End of generic plugin template files -->