"""
All `uids` are supposed to be pythonic function names (see PEP http://www.python.org/dev/peps/pep-0008/#function-names).
"""
__all__ = ('BaseDashboardLayout', 'BaseDashboardPlaceholder', 'BaseDashboardPlugin', \
'BaseDashboardPluginWidget', 'layout_registry', 'plugin_widget_registry', \
'get_registered_plugins', 'get_registered_plugin_uids', 'validate_plugin_uid', \
'get_registered_layouts', 'get_registered_layout_uids', 'get_layout', \
'validate_placeholder_uid', 'plugin_registry', 'ensure_autodiscover', \
'DashboardPluginFormBase', 'collect_widget_media')
import uuid
import json
from six import text_type
from django.forms import ModelForm
from django.forms.util import ErrorList
from django.http import Http404
from django.template.loader import render_to_string
from django.utils.encoding import force_text
from dash.discover import autodiscover
from dash.exceptions import LayoutDoesNotExist
from dash.settings import ACTIVE_LAYOUT, LAYOUT_CELL_UNITS, DEBUG
from dash.settings import DEFAULT_PLACEHOLDER_VIEW_TEMPLATE_NAME, DEFAULT_PLACEHOLDER_EDIT_TEMPLATE_NAME
from dash.exceptions import InvalidRegistryItemType
import logging
logger = logging.getLogger(__name__)
_ = lambda s: s
[docs]class BaseDashboardLayout(object):
"""
Base layout.
Layouts consist of placeholders.
:Properties:
- `uid` (str): Layout unique identifier (globally).
- `name` (str): Layout name.
- `description` (str): Layout description.
- `placeholders` (iterable): Iterable (list, tuple or set) of `dash.base.BaseDashboardPlaceholder`
subclasses.
- `view_template_name` (str): Temlate name used to render the layout (view).
- `edit_template_name` (str): Template named used to render the layout (edit).
- `html_classes` (str): Extra HTML class that layout should get.
- `cell_units` (str):
- `media_css` (list): List all specific stylesheets.
- `media_js` (list): List all specific javascripts.
"""
uid = None
name = None
description = None
placeholders = []
view_template_name = None
view_template_name_ajax = None
edit_template_name = None
edit_template_name_ajax = None
html_classes = []
cell_units = None # Most likely, it makes sense to define this on a layout level. Think of it.
media_css = []
media_js = []
def __init__(self, user=None):
"""
:param django.contrib.auth.models.User user:
"""
assert self.uid
assert self.name
assert self.view_template_name
assert self.edit_template_name
assert self.placeholders
assert self.cell_units and self.cell_units in LAYOUT_CELL_UNITS
assert isinstance(self.media_js, (list, tuple))
assert isinstance(self.media_css, (list, tuple))
if isinstance(self.media_js, tuple):
self.media_js = list(self.media_js)
if isinstance(self.media_css, tuple):
self.media_css = list(self.media_css)
self.placeholders_dict = {}
self.placeholder_uids = []
for placeholder in self.placeholders:
self.placeholders_dict.update({placeholder.uid: placeholder})
self.placeholder_uids.append(placeholder.uid)
self.user = user
self.widget_media_js = []
self.widget_media_css = []
[docs] def get_view_template_name(self, request=None):
if not self.view_template_name_ajax:
return self.view_template_name
elif request and request.is_ajax():
return self.view_template_name_ajax
else:
return self.view_template_name
[docs] def get_edit_template_name(self, request=None):
if not self.edit_template_name_ajax:
return self.edit_template_name
elif request and request.is_ajax():
return self.edit_template_name_ajax
else:
return self.edit_template_name
[docs] def get_placeholder(self, uid, default=None):
return self.placeholders_dict.get(uid, default)
[docs] def get_placeholders(self, request=None):
"""
Gets the list of placeholders registered for the layout.
:param django.http.HttpRequest request:
:return itetable: List of placeholder classes. Override in your layout if you need a custom behaviour.
"""
return self.placeholders
[docs] def get_placeholder_uids(self, request=None):
"""
Gets the list of placeholder uids.
:param django.http.HttpRequest request:
:return list:
"""
uids = []
for placeholder in self.placeholders:
uids.append(placeholder.uid)
return uids
[docs] def get_grouped_dashboard_entries(self, dashboard_entries):
"""
Gets dashboard entries grouped by placeholder.
:param iterable dashboard_entries: Iterable of `dash.models.DashboardEntry` objects.
:return list:
"""
entries = {}
if not dashboard_entries:
return entries
for dashboard_entry in dashboard_entries:
if not dashboard_entry.placeholder_uid in entries:
entries[dashboard_entry.placeholder_uid] = []
entries[dashboard_entry.placeholder_uid].append(dashboard_entry)
return entries
[docs] def get_placeholder_instances(self, dashboard_entries=None, workspace=None, request=None):
"""
Gets placeholder instances.
:param iterable dashboard_entries: Iterable of `dash.models.DashboardEntry` objects.
:param django.http.HttpRequest request:
:return list: List of `dash.base.BaseDashboardPlaceholder` subclassed instances.
"""
entries = self.get_grouped_dashboard_entries(dashboard_entries)
placeholder_instances = []
for placeholder_cls in self.get_placeholders(request):
placeholder = placeholder_cls(self)
placeholder.request = request
placeholder.workspace = workspace
if entries:
placeholder.dashboard_entries = entries.get(placeholder_cls.uid, None)
placeholder_instances.append(placeholder)
return placeholder_instances
@property
[docs] def primary_html_class(self):
return 'layout-{0}'.format(self.uid)
@property
[docs] def html_class(self):
"""
Class used in the HTML.
:return str:
"""
return '{0} {1}'.format(self.primary_html_class, ' '.join(self.html_classes))
[docs] def get_css(self, placeholders):
"""
Gets placeholder specific css.
:param iterable placeholders: Iterable of `dash.base.BaseDashboardPlaceholder` subclassed instances.
:return str:
"""
css = []
for placeholder in placeholders:
css.append(placeholder.css)
return '\n'.join(css)
[docs] def render_for_view(self, dashboard_entries=None, workspace=None, request=None):
"""
Renders the layout.
:param iterable dashboard_entries:
:param str workspace: Current workspace.
:param django.http.HttpRequest request:
:return str:
"""
placeholders = self.get_placeholder_instances(dashboard_entries, workspace, request)
context = {
'placeholders': placeholders,
'request': request,
'css': self.get_css(placeholders)
}
return render_to_string(self.get_view_template_name(request), context)
[docs] def render_for_edit(self, dashboard_entries=None, workspace=None, request=None):
"""
Renders the layout.
:param iterable dashboard_entries:
:param str workspace: Current workspace.
:param django.http.HttpRequest request:
:return str:
"""
placeholders = self.get_placeholder_instances(dashboard_entries, workspace, request)
context = {'placeholders': placeholders, 'request': request, 'css': self.get_css(placeholders)}
return render_to_string(self.get_edit_template_name(request), context)
[docs]class BaseDashboardPlaceholder(object):
"""
Base placeholder.
:Properties:
- `uid` (str): Unique identifier (shouldn't repeat within a single layout).
"""
uid = None
cols = None #1
rows = None #8
cell_width = None #100
cell_height = None #100
view_template_name = ''
edit_template_name = ''
html_classes = []
def __init__(self, layout):
assert self.uid
assert self.rows
assert self.cols
assert self.cell_width
assert self.cell_height
self.layout = layout
self.dashboard_entries = None
self.request = None
@property
[docs] def cell_units(self):
return self.layout.cell_units
@property
[docs] def html_id(self):
"""
ID used in the HTML. Unique.
:return str:
"""
return 'id_{0}'.format(self.uid)
@property
[docs] def primary_html_class(self):
return 'placeholder-{0}'.format(self.uid)
@property
[docs] def html_class(self):
"""
Class used in the HTML.
:return str:
"""
return '{0} {1}'.format(self.primary_html_class, ' '.join(self.html_classes))
[docs] def get_view_template_name(self):
return self.view_template_name if self.view_template_name else DEFAULT_PLACEHOLDER_VIEW_TEMPLATE_NAME
[docs] def get_edit_template_name(self):
return self.edit_template_name if self.view_template_name else DEFAULT_PLACEHOLDER_EDIT_TEMPLATE_NAME
[docs] def load_dashboard_entries(self, dashboard_entries=None):
"""
Feed the dashboard entries to the layout for rendering later.
:param iterable dashboard_entries: Iterable of `dash.models.DashboardEntry` objects.
"""
self.dashboard_entries = dashboard_entries
[docs] def render_for_view(self):
"""
Renders the placeholder for view mode.
:return str:
"""
context = {
'placeholder': self,
'dashboard_entries': self.dashboard_entries,
'request': self.request,
'workspace': self.workspace
}
return render_to_string(self.get_view_template_name(), context)
def _generate_widget_cells(self):
"""
Generates widget cells. Returns a list of tuples, where the first element represents the cell class
and the second element represents the cell position.
:param str workspace: Current workspace slug.
:param django.http.HttpRequest request:
:return list:
"""
empty_cells = []
position = 1
for row in range(1, self.rows + 1):
for col in range(1, self.cols + 1):
empty_cells.append(('col-{0} row-{1}'.format(col, row), position))
position += 1
return empty_cells
[docs] def render_for_edit(self):
"""
Renders the placeholder for edit mode.
:param str workspace: Current workspace slug.
:param django.http.HttpRequest request:
:return str:
"""
context = {
'placeholder': self,
'dashboard_entries': self.dashboard_entries,
'request': self.request,
'workspace': self.workspace,
'widget_cells': self._generate_widget_cells()
}
return render_to_string(self.get_edit_template_name(), context)
@property
[docs] def css(self):
"""
CSS styles for the placeholders and plugins. The placeholder dimensions as well as columns sizes,
should be handled here. Since we are in a placeholder and a placeholder has a defined number of
rows and columns and each reneder has just a fixed amount of rows and columns defined, we can
render the top left corners generic css classes.
Cells do NOT have margins or paddings. This is essential (since all the plugins are positioned
absolutely). If you want to have padding in your plugin widget, specify the `plugin-content-wrapper`
class style in your specific layout/theme.
:example:
.placeholder .plugin .plugin-content-wrapper {
padding: 5px;
}
:return str:
"""
def placeholder_width():
"""
Placeholder width.
:return str:
"""
return '{0}{1}'.format(self.cols * self.cell_width, self.cell_units)
def placeholder_height():
"""
Placeholder height.
:return str:
"""
return '{0}{1}'.format(self.rows * self.cell_height, self.cell_units)
def plugin_width():
"""
Default width of a plugin widget (1 cell).
:return str:
"""
return '{0}{1}'.format(self.cell_width, self.cell_units)
def plugin_height():
"""
Default height of a plugin widget (1 cell).
:return str:
"""
return '{0}{1}'.format(self.cell_height, self.cell_units)
def plugin_positions():
"""
Plugin positions depending on the row and cell occupied. All plugins are positioned absolutely.
Based on the row, we use `margin-top` and `margin-left` to position a plugin.
..:Used CSS classes:
- `row-1`, `row-2`, etc.
- `col-1`, `col-2`, etc.
:return str:
"""
positions = []
for row_num in range(0, self.rows):
s = """
.placeholder.{placeholder_class} .empty-widget-cell.row-{row_num},
.placeholder.{placeholder_class} .plugin.row-{row_num} {{
margin-top: {top};
}}
""".format(
placeholder_class = self.primary_html_class,
row_num = (row_num + 1),
top = '{0}{1}'.format(self.cell_height * row_num, self.cell_units)
)
positions.append(s)
for col_num in range(0, self.cols):
s = """
.placeholder.{placeholder_class} .empty-widget-cell.col-{col_num},
.placeholder.{placeholder_class} .plugin.col-{col_num} {{
margin-left: {left};
}}
""".format(
placeholder_class = self.primary_html_class,
col_num = (col_num + 1),
left = '{0}{1}'.format(self.cell_width * col_num, self.cell_units)
)
positions.append(s)
return '\n'.join(positions)
def plugin_sizes():
"""
Plugin size based on its' `rows` and `cols` properties.
..:Used CSS classes:
- `width-1`, `width-2`, etc.
- `height-1`, `height-2`, etc.
:return str:
"""
sizes = []
for row_num in range(0, self.rows):
s = """
.placeholder.{placeholder_class} .plugin.height-{row_num} {{
height: {height};
}}
""".format(
placeholder_class = self.primary_html_class,
row_num = (row_num + 1),
height = '{0}{1}'.format(self.cell_height * (row_num + 1), self.cell_units)
)
sizes.append(s)
for col_num in range(0, self.cols):
s = """
.placeholder.{placeholder_class} .plugin.width-{col_num} {{
width: {width};
}}
""".format(
placeholder_class = self.primary_html_class,
col_num = (col_num + 1),
width = '{0}{1}'.format(self.cell_width * (col_num + 1), self.cell_units)
)
sizes.append(s)
return '\n'.join(sizes)
def empty_cell_size():
"""
CSS for empty cell size.
"""
s = """
.placeholder.{placeholder_class} .empty-widget-cell {{
width: {width};
height: {height};
line-height: {height};
}}
""".format(
placeholder_class = self.primary_html_class,
width = '{0}{1}'.format(self.cell_width, self.cell_units),
height = '{0}{1}'.format(self.cell_height, self.cell_units)
)
return s
css = """
.placeholder.{placeholder_class} {{
width: {placeholder_width};
height: {placeholder_height};
}}
.placeholder.{placeholder_class} .plugin {{
width: {plugin_width};
height: {plugin_height};
}}
{plugin_positions}
{plugin_sizes}
{empty_cell_sizes}
""".format(
placeholder_class = self.primary_html_class,
placeholder_width = placeholder_width(),
placeholder_height = placeholder_height(),
plugin_width = plugin_width(),
plugin_height = plugin_height(),
plugin_positions = plugin_positions(),
plugin_sizes = plugin_sizes(),
empty_cell_sizes = empty_cell_size()
)
return css
class DashboardPluginDataStorage(object):
"""
Storage for plugin data.
"""
[docs]class BaseDashboardPlugin(object):
"""
Base dashboard plugin from which every plugin should inherit.
:Properties:
- `uid` (str): Plugin uid (obligatory). Example value: 'dummy', 'wysiwyg', 'news'.
- `name` (str): Plugin name (obligatory). Example value: 'Dummy plugin',
'WYSIWYG', 'Latest news'.
- `description` (str): Plugin decription (optional). Example value: 'Dummy plugin used just for testing'.
- `help_text` (str): Plugin help text (optional). This text would be shown in
``dash.views.add_dashboard_entry`` and ``dash.views.edit_dashboard_entry`` views.
- `form`: Plugin form (optional). A subclass of ``django.forms.Form``. Should be given
in case plugin is configurable.
- `add_form_template` (str) (optional): Add form template (optional). If given, overrides the
`dash.views.add_dashboard_entry` default template.
- `edit_form_template` (str): Edit form template (optional). If given, overrides the
`dash.views.edit_dashboard_entry` default template.
- `html_classes` (list): List of extra HTML classes for the plugin.
- `group` (str): Plugin are grouped under the specified group. Override in your plugin if necessary.
"""
uid = None
name = None
description = None
help_text = None
form = None
add_form_template = None
edit_form_template = None
html_classes = []
group = _("General")
def __init__(self, layout_uid, placeholder_uid, workspace=None, user=None, position=None):
"""
:param str placeholder_uid: Unique identifier of plugin placeholder (layout.placeholder).
:param dash.models.DashboardWorkspace workspace: Plugin workspace.
:param django.contrib.auth.models.User user: Plugin owner.
"""
# Making sure all necessary properties are defined.
try:
assert self.uid
assert self.name
except Exception as e:
raise NotImplementedError(
"You should define `uid` and `name` properties in your `{0}.{1}` class.".format(
self.__class__.__module__, self.__class__.__name__
)
)
layout_cls = layout_registry.get(layout_uid, None)
self.layout = layout_cls() if layout_cls else None
placeholder_cls = self.layout.get_placeholder(placeholder_uid)
self.placeholder = placeholder_cls(self.layout) if placeholder_cls else None
if not (self.layout and self.placeholder):
raise Exception(
"Invalid placeholder value {0} in your `{1}.{2}` class.".format(
placeholder_uid, self.__class__.__module__, self.__class__.__name__
)
)
self.layout_uid = layout_uid
self.placeholder_uid = placeholder_uid
self.workspace = workspace
self.user = user
self.position = position
# Some initial values
self.request = None
self.data = DashboardPluginDataStorage()
self._html_id = 'p{0}'.format(uuid.uuid4())
@property
[docs] def html_id(self):
return self._html_id
[docs] def get_position(self):
"""
Gets the exact position of the plugin widget in the placeholder (row number, col number).
:return tuple: Tuple of row and col numbers.
"""
col = self.position % self.placeholder.cols
row = int(self.position / self.placeholder.cols) + (1 if col > 0 else 0)
if col == 0:
col = self.placeholder.cols
return (row, col)
@property # Comment the @property if something goes wrong.
[docs] def html_class(self):
"""
A massive work on positioning the plugin and having it to be displayed in a given width is
done here. We should be getting the plugin widget for the plugin given and based on its'
properties (static!) as well as on plugin position (which we have from model), we can show
the plugin with the exact class.
"""
try:
widget = self.get_widget()
html_class = ['plugin-{0} {1} {2}'.format(self.uid, widget.html_class, ' '.join(self.html_classes))]
html_class.append('width-{0}'.format(widget.cols))
html_class.append('height-{0}'.format(widget.rows))
row, col = self.get_position()
html_class.append('row-{0}'.format(row))
html_class.append('col-{0}'.format(col))
return ' '.join(html_class)
except Exception as e:
logger.debug(str(e))
[docs] def process(self, plugin_data=None, fetch_related_data=False):
"""
Init plugin with data.
"""
try:
# Calling pre-processor.
self.pre_processor()
if plugin_data:
try:
# Trying to load the plugin data to JSON.
plugin_data = json.loads(plugin_data)
# If a valid JSON object, feed it to our plugin and process the data. The
# ``process_data`` method should be defined in your subclassed plugin class.
if plugin_data:
self.load_plugin_data(plugin_data)
self.process_plugin_data(fetch_related_data=fetch_related_data)
except Exception as e:
if DEBUG:
logger.debug(str(e))
# Calling the post processor.
self.post_processor()
return self
except Exception as e:
if DEBUG:
logger.debug(str(e))
[docs] def load_plugin_data(self, plugin_data):
"""
Loads the plugin data saved in ``dash.models.DashboardEntry``. Plugin data is saved in JSON
string.
:param str plugin_data: JSON string with plugin data.
"""
self.plugin_data = plugin_data
def _process_plugin_data(self, fields, fetch_related_data=False):
"""
Process the plugin data. Override if need customisations.
Beware, this is not always called.
"""
for field, default_value in fields:
try:
setattr(self.data, field, self.plugin_data.get(field, default_value))
except Exception as e:
setattr(self.data, field, default_value)
[docs] def process_plugin_data(self, fetch_related_data=False):
"""
Processes the plugin data.
"""
form = self.get_form()
return self._process_plugin_data(form.plugin_data_fields, fetch_related_data=fetch_related_data)
def _get_plugin_form_data(self, fields):
"""
Gets plugin data.
:param iterable fields: List of tuples to iterate.
:return dict:
"""
form_data = {}
for field, default_value in fields:
try:
form_data.update({field: self.plugin_data.get(field, default_value)})
except Exception as e:
if DEBUG:
logger.debug(e)
return form_data
[docs] def get_instance(self):
return None
[docs] def render(self, request=None):
"""
Renders the plugin HTML (for dashboard workspace).
:param django.http.HttpRequest request:
:return str:
"""
widget_cls = self.get_widget()
if widget_cls:
widget = widget_cls(self)
render = widget.render(request=request)
return render or ''
elif DEBUG:
logger.debug("No widget defined for {0}.{1}.{2}".format(self.layout.uid, self.placeholder.uid, self.uid))
[docs] def update_plugin_data(self, dashboard_entry):
"""
Used in ``dash.management.commands.dash_update_plugin_data``.
Some plugins would contain data fetched from various sources (models, remote data). Since dashboard
entries are by definition loaded extremely much, you are advised to store as much data as possible in
``plugin_data`` field of ``dash.models.DashboardEntry``. Some externally fetched data becomes invalid
after some time and needs updating. For that purpose, in case if your plugin needs that, redefine this
method in your plugin. If you need your data to be periodically updated, add a cron-job which would
run ``dash_update_plugin_data`` management command (see
``dash.management.commands.dash_update_plugin_data`` module).
:param dash.models.DashboardEntry: Instance of ``dash.models.DashboardEntry``.
"""
pass
[docs] def delete_plugin_data(self):
"""
Used in ``dash.views.delete_dashboard_entry``. Fired automatically, when ``dash.models.DashboardEntry``
object is about to be deleted. Make use of it if your plugin creates database records that are not
monitored externally but by dash only.
"""
[docs] def pre_processor(self):
"""
Redefine in your subclassed plugin when necessary.
Pre process plugin data (before rendering). This method is being called before the data has been
loaded into the plugin.
Note, that request (django.http.HttpRequest) is available (self.request).
"""
[docs] def post_processor(self):
"""
Redefine in your subclassed plugin when necessary.
Post process plugin data here (before rendering). This methid is being called after the data has been
loaded into the plugin.
Note, that request (django.http.HttpRequest) is available (self.request).
"""
class MetaBaseDashboardPluginWidget(type):
"""
Meta class for ``dash.base.BaseDashboardPluginWidget``.
"""
@property
def html_class(cls):
"""
HTML class of the ``dash.base.BaseDashboardPluginWidget``.
:return str:
"""
return ' '.join(cls.html_classes)
class classproperty(property):
def __get__(self, cls, owner):
return classmethod(self.fget).__get__(None, owner)()
class BaseRegistry(object):
"""
Registry of dash plugins. It's essential, that class registered has the ``uid`` property.
"""
type = None
def __init__(self):
assert self.type
self._registry = {}
self._forced = []
def register(self, cls, force=False):
"""
Registers the plugin in the registry.
:param mixed.
"""
if not issubclass(cls, self.type):
raise InvalidRegistryItemType("Invalid item type `{0}` for registry `{1}`".format(cls, self.__class__))
# If item has not been forced yet, add/replace its' value in the registry
if force:
if not cls.uid in self._forced:
self._registry[cls.uid] = cls
self._forced.append(cls.uid)
return True
else:
return False
else:
if cls.uid in self._registry:
return False
else:
self._registry[cls.uid] = cls
return True
def unregister(self, cls):
if not issubclass(cls, self.type):
raise InvalidRegistryItemType("Invalid item type `{0}` for registry `{1}`".format(cls, self.__class__))
# Only non-forced items are allowed to be unregistered.
if cls.uid in self._registry and not cls.uid in self._forced:
self._registry.pop(cls.uid)
return True
else:
return False
def get(self, uid, default=None):
"""
Gets the given entry from the registry.
:param str uid:
:return mixed.
"""
item = self._registry.get(uid, default)
if not item:
logger.debug("Can't find plugin with uid `{0}` in `{1}` registry".format(uid, self.__class__))
return item
class PluginRegistry(BaseRegistry):
"""
Plugin registry.
"""
type = BaseDashboardPlugin
class LayoutRegistry(BaseRegistry):
"""
Layout registry.
"""
type = BaseDashboardLayout
class PluginWidgetRegistry(object):
"""
Registry of dash plugins widgets (renderers).
"""
type = BaseDashboardPluginWidget
def __init__(self):
assert self.type
self._registry = {}
self._forced = []
@staticmethod
def namify(layout, placeholder, plugin_uid):
return '{0}.{1}.{2}'.format(layout, placeholder, plugin_uid)
def register(self, cls, force=False):
"""
Registers the plugin renderer in the registry.
:param dash.base.BasePluginRenderer cls: Subclass of `dash.base.BasePluginRenderer`.
"""
if not issubclass(cls, self.type):
raise InvalidRegistryItemType("Invalid item type `{0}` for registry `{1}`".format(cls, self.__class__))
uid = PluginWidgetRegistry.namify(cls.layout_uid, cls.placeholder_uid, cls.plugin_uid)
# If item has not been forced yet, add/replace its' value in the registry
if force:
if not uid in self._forced:
self._registry[uid] = cls
self._forced.append(uid)
return True
else:
return False
else:
if uid in self._registry:
return False
else:
self._registry[uid] = cls
return True
def unregister(self, cls):
if not issubclass(cls, self.type):
raise InvalidRegistryItemType("Invalid item type `{0}` for registry `{1}`".format(cls, self.__class__))
uid = PluginWidgetRegistry.namify(cls.layout_uid, cls.placeholder_uid, cls.plugin_uid)
# Only non-forced items are allowed to be unregistered.
if uid in self._registry and not uid in self._forced:
self._registry.pop(uid)
return True
else:
return False
def get(self, uid, default=None):
"""
Gets the given entry from the registry.
:param str uid:
:return mixed.
"""
item = self._registry.get(uid, default)
if not item:
logger.debug("Can't find plugin with uid `{0}` in `{1}` registry".format(uid, self.__class__))
return item
# Register plugins by calling plugin_registry.register()
plugin_registry = PluginRegistry()
# Register layouts by calling layout_registry.register()
layout_registry = LayoutRegistry()
# Register of plugin widgets.
plugin_widget_registry = PluginWidgetRegistry()
[docs]def ensure_autodiscover():
"""
Ensures that plugins are autodiscovered.
"""
if not (plugin_registry._registry and layout_registry._registry and plugin_widget_registry._registry):
autodiscover()
[docs]def get_registered_plugins():
"""
Gets a list of registered plugins in a form if tuple (plugin name, plugin description). If not yet
autodiscovered, autodiscovers them.
:return list:
"""
ensure_autodiscover()
registered_plugins = []
for uid, plugin in plugin_registry._registry.items():
registered_plugins.append((uid, force_text(plugin.name)))
return registered_plugins
[docs]def get_registered_plugin_uids():
"""
Gets a list of registered plugin uids as a list . If not yet autodiscovered, autodiscovers them.
:return list:
"""
ensure_autodiscover()
registered_plugins = []
for uid, plugin in plugin_registry._registry.items():
registered_plugins.append(uid)
return registered_plugins
[docs]def validate_placeholder_uid(layout, placeholder_uid):
"""
Validates the placeholder.
:param str layout_uid:
:param str placeholder_uid:
:return bool:
"""
return placeholder_uid in layout.placeholder_uids
[docs]def validate_plugin_uid(plugin_uid):
"""
Validates the plugin uid.
:param str plugin_uid:
:return bool:
"""
return plugin_uid in get_registered_plugin_uids()
[docs]def get_registered_layouts():
"""
Gets registered layouts.
"""
ensure_autodiscover()
registered_layouts = []
for uid, layout in layout_registry._registry.items():
registered_layouts.append((uid, force_text(layout.name)))
return registered_layouts
[docs]def get_registered_layout_uids():
"""
Gets uids of registered layouts.
"""
return layout_registry._registry.keys()
[docs]def get_layout(layout_uid=None, as_instance=False):
"""
Gets the layout by ``layout_uid`` given. If left empty, takes the default one chosen in settings.
Raises a ``dash.exceptions.NoActiveLayoutChosen`` when no default layout could be found.
:return dash.base.BaseDashboardLayout: Sublcass of `dash.base.BaseDashboardLayout`.
"""
ensure_autodiscover()
if not layout_uid:
layout_uid = ACTIVE_LAYOUT
layout_cls = layout_registry.get(layout_uid, None)
if not layout_cls:
raise LayoutDoesNotExist(_("Layout `{0}` does not exist!").format(layout_uid))
if as_instance:
return layout_cls()
return layout_cls